@openauditmodel/cli 0.1.0

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.
Files changed (134) hide show
  1. package/LICENSE +215 -0
  2. package/README.md +647 -0
  3. package/dist/conformance/src/cli.d.ts +20 -0
  4. package/dist/conformance/src/cli.js +560 -0
  5. package/dist/conformance/src/cli.js.map +1 -0
  6. package/dist/conformance/src/format-errors.d.ts +20 -0
  7. package/dist/conformance/src/format-errors.js +116 -0
  8. package/dist/conformance/src/format-errors.js.map +1 -0
  9. package/dist/conformance/src/integrity/canonicalize.d.ts +25 -0
  10. package/dist/conformance/src/integrity/canonicalize.js +100 -0
  11. package/dist/conformance/src/integrity/canonicalize.js.map +1 -0
  12. package/dist/conformance/src/integrity/digest.d.ts +54 -0
  13. package/dist/conformance/src/integrity/digest.js +126 -0
  14. package/dist/conformance/src/integrity/digest.js.map +1 -0
  15. package/dist/conformance/src/integrity/types.d.ts +86 -0
  16. package/dist/conformance/src/integrity/types.js +36 -0
  17. package/dist/conformance/src/integrity/types.js.map +1 -0
  18. package/dist/conformance/src/integrity/verify-chain.d.ts +23 -0
  19. package/dist/conformance/src/integrity/verify-chain.js +235 -0
  20. package/dist/conformance/src/integrity/verify-chain.js.map +1 -0
  21. package/dist/conformance/src/integrity/verify-event.d.ts +29 -0
  22. package/dist/conformance/src/integrity/verify-event.js +140 -0
  23. package/dist/conformance/src/integrity/verify-event.js.map +1 -0
  24. package/dist/conformance/src/privacy/entropy.d.ts +14 -0
  25. package/dist/conformance/src/privacy/entropy.js +86 -0
  26. package/dist/conformance/src/privacy/entropy.js.map +1 -0
  27. package/dist/conformance/src/privacy/field-names.d.ts +36 -0
  28. package/dist/conformance/src/privacy/field-names.js +121 -0
  29. package/dist/conformance/src/privacy/field-names.js.map +1 -0
  30. package/dist/conformance/src/privacy/lint-event.d.ts +18 -0
  31. package/dist/conformance/src/privacy/lint-event.js +272 -0
  32. package/dist/conformance/src/privacy/lint-event.js.map +1 -0
  33. package/dist/conformance/src/privacy/rules.d.ts +44 -0
  34. package/dist/conformance/src/privacy/rules.js +161 -0
  35. package/dist/conformance/src/privacy/rules.js.map +1 -0
  36. package/dist/conformance/src/privacy/safe-formats.d.ts +32 -0
  37. package/dist/conformance/src/privacy/safe-formats.js +118 -0
  38. package/dist/conformance/src/privacy/safe-formats.js.map +1 -0
  39. package/dist/conformance/src/privacy/size-analysis.d.ts +36 -0
  40. package/dist/conformance/src/privacy/size-analysis.js +87 -0
  41. package/dist/conformance/src/privacy/size-analysis.js.map +1 -0
  42. package/dist/conformance/src/privacy/token-patterns.d.ts +24 -0
  43. package/dist/conformance/src/privacy/token-patterns.js +126 -0
  44. package/dist/conformance/src/privacy/token-patterns.js.map +1 -0
  45. package/dist/conformance/src/privacy/traverse.d.ts +33 -0
  46. package/dist/conformance/src/privacy/traverse.js +61 -0
  47. package/dist/conformance/src/privacy/traverse.js.map +1 -0
  48. package/dist/conformance/src/privacy/types.d.ts +58 -0
  49. package/dist/conformance/src/privacy/types.js +32 -0
  50. package/dist/conformance/src/privacy/types.js.map +1 -0
  51. package/dist/conformance/src/privacy/url-analysis.d.ts +40 -0
  52. package/dist/conformance/src/privacy/url-analysis.js +148 -0
  53. package/dist/conformance/src/privacy/url-analysis.js.map +1 -0
  54. package/dist/conformance/src/profiles/check-profile.d.ts +22 -0
  55. package/dist/conformance/src/profiles/check-profile.js +72 -0
  56. package/dist/conformance/src/profiles/check-profile.js.map +1 -0
  57. package/dist/conformance/src/profiles/evaluate-rule.d.ts +27 -0
  58. package/dist/conformance/src/profiles/evaluate-rule.js +87 -0
  59. package/dist/conformance/src/profiles/evaluate-rule.js.map +1 -0
  60. package/dist/conformance/src/profiles/load-profile.d.ts +30 -0
  61. package/dist/conformance/src/profiles/load-profile.js +95 -0
  62. package/dist/conformance/src/profiles/load-profile.js.map +1 -0
  63. package/dist/conformance/src/profiles/resolve-pointer.d.ts +44 -0
  64. package/dist/conformance/src/profiles/resolve-pointer.js +123 -0
  65. package/dist/conformance/src/profiles/resolve-pointer.js.map +1 -0
  66. package/dist/conformance/src/profiles/select-rules.d.ts +13 -0
  67. package/dist/conformance/src/profiles/select-rules.js +42 -0
  68. package/dist/conformance/src/profiles/select-rules.js.map +1 -0
  69. package/dist/conformance/src/profiles/types.d.ts +101 -0
  70. package/dist/conformance/src/profiles/types.js +31 -0
  71. package/dist/conformance/src/profiles/types.js.map +1 -0
  72. package/dist/conformance/src/profiles/validate-profile-definition.d.ts +12 -0
  73. package/dist/conformance/src/profiles/validate-profile-definition.js +47 -0
  74. package/dist/conformance/src/profiles/validate-profile-definition.js.map +1 -0
  75. package/dist/conformance/src/sources.d.ts +53 -0
  76. package/dist/conformance/src/sources.js +153 -0
  77. package/dist/conformance/src/sources.js.map +1 -0
  78. package/dist/conformance/src/validate-core.d.ts +30 -0
  79. package/dist/conformance/src/validate-core.js +62 -0
  80. package/dist/conformance/src/validate-core.js.map +1 -0
  81. package/dist/conformance/src/validate.d.ts +34 -0
  82. package/dist/conformance/src/validate.js +62 -0
  83. package/dist/conformance/src/validate.js.map +1 -0
  84. package/dist/conformance/src/validator-interface.d.ts +35 -0
  85. package/dist/conformance/src/validator-interface.js +19 -0
  86. package/dist/conformance/src/validator-interface.js.map +1 -0
  87. package/package.json +85 -0
  88. package/profiles/README.md +201 -0
  89. package/profiles/api-and-integration-management/README.md +271 -0
  90. package/profiles/api-and-integration-management/profile.json +258 -0
  91. package/profiles/backup-and-recovery/README.md +318 -0
  92. package/profiles/backup-and-recovery/profile.json +178 -0
  93. package/profiles/customer-and-account-management/README.md +340 -0
  94. package/profiles/customer-and-account-management/profile.json +237 -0
  95. package/profiles/deployment-and-change-management/README.md +359 -0
  96. package/profiles/deployment-and-change-management/profile.json +298 -0
  97. package/profiles/document-management/README.md +120 -0
  98. package/profiles/document-management/profile.json +170 -0
  99. package/profiles/financial-transaction-management/README.md +339 -0
  100. package/profiles/financial-transaction-management/profile.json +247 -0
  101. package/profiles/identity-and-access-management/README.md +112 -0
  102. package/profiles/identity-and-access-management/profile.json +120 -0
  103. package/profiles/incident-management/README.md +338 -0
  104. package/profiles/incident-management/profile.json +256 -0
  105. package/profiles/message-broker-management/README.md +344 -0
  106. package/profiles/message-broker-management/profile.json +399 -0
  107. package/profiles/profile-definition.schema.json +213 -0
  108. package/profiles/secrets-and-key-management/README.md +331 -0
  109. package/profiles/secrets-and-key-management/profile.json +219 -0
  110. package/schemas/v0.1/audit-event.schema.json +911 -0
  111. package/semantic-conventions/README.md +52 -0
  112. package/semantic-conventions/authentication.md +111 -0
  113. package/semantic-conventions/configuration-and-change.md +122 -0
  114. package/semantic-conventions/correlation-and-tracing.md +387 -0
  115. package/semantic-conventions/data-access.md +115 -0
  116. package/semantic-conventions/event-naming.md +114 -0
  117. package/semantic-conventions/identity-and-access.md +112 -0
  118. package/semantic-conventions/privileged-operations.md +110 -0
  119. package/semantic-conventions/workflow-and-approval.md +109 -0
  120. package/specification/actor-model.md +135 -0
  121. package/specification/approval-and-delegation.md +150 -0
  122. package/specification/authentication.md +116 -0
  123. package/specification/authorization.md +117 -0
  124. package/specification/change-model.md +134 -0
  125. package/specification/delivery.md +136 -0
  126. package/specification/design-principles.md +137 -0
  127. package/specification/event-model.md +387 -0
  128. package/specification/evidence-model.md +109 -0
  129. package/specification/extension-model.md +159 -0
  130. package/specification/integrity.md +314 -0
  131. package/specification/overview.md +172 -0
  132. package/specification/privacy.md +386 -0
  133. package/specification/resource-model.md +116 -0
  134. package/specification/terminology.md +135 -0
package/README.md ADDED
@@ -0,0 +1,647 @@
1
+ <p align="center">
2
+ <img src="assets/logo.png" alt="OpenAuditModel" width="420">
3
+ </p>
4
+
5
+ # OpenAuditModel
6
+
7
+ > **OpenAuditModel defines a common, verifiable and backend-independent audit event model for
8
+ > business applications.**
9
+ >
10
+ > **One audit model for every application.**
11
+
12
+ | | |
13
+ | ------------------------- | ---------------------------------------------------------------- |
14
+ | **Specification version** | 0.1 |
15
+ | **Project status** | **Experimental** |
16
+ | **Production readiness** | **Not production-ready** |
17
+ | **Compliance** | **No compliance guarantee** |
18
+ | **Canonical schema** | `https://openauditmodel.org/schemas/audit-event/0.1/schema.json` |
19
+ | **License** | Apache License 2.0 |
20
+
21
+ Version 0.1 is an experimental specification. Field names, constraints, vocabularies and the
22
+ compatibility strategy may all change. Nothing here constitutes legal advice, and conformance to this
23
+ specification is not compliance with any law, regulation, standard or contract.
24
+
25
+ ---
26
+
27
+ ## Quick start
28
+
29
+ Not yet published to a package registry. One clone, one build, then commands against any file.
30
+ Requires Node.js 22 or newer.
31
+
32
+ ```bash
33
+ git clone https://github.com/OpenAuditModel/OpenAuditModel.git
34
+ cd OpenAuditModel && npm install && npm run build
35
+
36
+ cat > audit-event.json <<'EOF'
37
+ {
38
+ "specVersion": "0.1",
39
+ "id": "018f1b70-2c18-7f3a-b46d-5e8a1c9d0b12",
40
+ "time": "2026-03-14T11:47:52.108Z",
41
+ "event": {
42
+ "name": "financial.transfer.execute",
43
+ "category": "data-modification",
44
+ "outcome": "success"
45
+ },
46
+ "actor": { "type": "user", "id": "user-5120" },
47
+ "resource": { "type": "money-transfer", "id": "transfer-2026-004418" },
48
+ "application": { "name": "payments-api", "environment": "production" }
49
+ }
50
+ EOF
51
+
52
+ npm run auditmodel -- validate audit-event.json
53
+ npm run auditmodel -- lint-privacy audit-event.json
54
+ npm run auditmodel -- check-profile audit-event.json --profile financial-transaction-management
55
+ ```
56
+
57
+ The binary is `auditmodel` once built (`node dist/conformance/src/cli.js` directly, or `npm run
58
+ auditmodel --` as above); `openauditmodel` is accepted as an alias. The profile is passed with
59
+ `--profile`, never positionally.
60
+
61
+ That third command **fails**, and it is meant to. The event is schema-valid but the financial profile
62
+ requires an authorization decision, a correlation identifier, a transaction reference, an amount, a
63
+ currency, a direction, a status and a linked resource. The output names each missing field with a
64
+ JSON Pointer. Adding them is the point of the exercise:
65
+
66
+ ```jsonc
67
+ "authorization": { "decision": "allow" },
68
+ "request": { "correlationId": "transfer-2026-004418" },
69
+ "relatedResources": [{ "type": "account", "id": "account-ref-781" }],
70
+ "metadata": {
71
+ "financial": {
72
+ "transactionId": "txn-2026-0314-0091",
73
+ "amount": 1250.5,
74
+ "currency": "EUR",
75
+ "direction": "outbound",
76
+ "status": "settled"
77
+ }
78
+ }
79
+ ```
80
+
81
+ With those added the event conforms and `check-profile` exits `0`. Note what the profile asked for
82
+ and what it did not: an amount and a currency, but no account number, no counterparty name and no
83
+ payment instruction. A profile requires the fields that make an operation reviewable, not the
84
+ business record itself.
85
+
86
+ ### Exit codes
87
+
88
+ The same contract across every command, so a CI job can branch on it:
89
+
90
+ | Code | Meaning |
91
+ | ---- | ------------------------------------------------------------------------------------ |
92
+ | `0` | A verdict was produced and it passed. |
93
+ | `1` | A verdict was produced and it failed — invalid, a privacy finding, a rule violation. |
94
+ | `2` | The tool could not run: unreadable file, bad arguments, unknown profile. |
95
+ | `3` | **No verdict was produced.** Nothing was evaluated. |
96
+
97
+ `3` is the one worth understanding, because it is the code that is easy to misread as success:
98
+
99
+ - `check-profile` returns it when **no rule in the profile governs the event**. That usually means the
100
+ event name does not match the profile's vocabulary — `transfer.created` instead of
101
+ `financial.transfer.execute`, say. **Not-applicable is not conformance.** The profile said nothing
102
+ about this event; it did not approve it.
103
+ - `lint-privacy` returns it when the input **is not an OpenAuditModel event**, so nothing was scanned.
104
+ The linter reads the locations the specification defines on an audit event; point it at an
105
+ arbitrary application log and it has nowhere to look. It reports that plainly rather than
106
+ reporting `clean`.
107
+
108
+ ```bash
109
+ # conforming → 0 the profile's rules were checked and passed
110
+ # violations → 1 the event matched the profile and failed a rule
111
+ # not applicable → 3 the event name matched no rule in the profile
112
+ npm run auditmodel -- check-profile audit-event.json --profile document-management
113
+ ```
114
+
115
+ That last command returns `3` for the financial event above: `document-management` governs no
116
+ `financial.*` event. Running an event against the wrong profile can never produce a pass.
117
+
118
+ ### Exporting an existing audit trail
119
+
120
+ Adoption does not require changing where you store anything. The lowest-friction path is an export
121
+ mapper, which is also the shape enterprise customers ask for:
122
+
123
+ ```text
124
+ existing audit database → mapper → OpenAuditModel NDJSON → customer, archive or SIEM
125
+ ```
126
+
127
+ `validate`, `lint-privacy` and `check-profile` all accept `.ndjson` and `.jsonl`, so the export can be
128
+ checked in CI before it is handed to anyone.
129
+
130
+ ## What is OpenAuditModel?
131
+
132
+ OpenAuditModel is an open, vendor-neutral specification for the structure of an audit event: a
133
+ record of an auditable operation performed in a business application.
134
+
135
+ It consists of:
136
+
137
+ - A **normative specification** describing what an audit event is and what a producer must record.
138
+ - A **canonical JSON Schema** (Draft 2020-12) that machine-verifies the structure.
139
+ - A **conformance toolchain** — a CLI, fixtures and tests — so that conformance is provable rather
140
+ than asserted.
141
+
142
+ The model itself is not a product, a service, a library or a pipeline: an audit event is valid with
143
+ nothing deployed. The repository does also contain an optional MCP server for tooling, which is not
144
+ part of the specification and which nobody needs in order to conform.
145
+
146
+ ## What problem does it solve?
147
+
148
+ Almost every application records auditable operations, and almost every application invents its own
149
+ shape for them. The consequences are familiar:
150
+
151
+ - Audit records cannot be validated, so defects are found years later, in retained data.
152
+ - Two systems' audit trails cannot be read together without bespoke translation.
153
+ - Every new application re-litigates the same questions: what is an actor, how do we record acting on
154
+ behalf of someone, where does the approval go.
155
+ - Audit data accumulates secrets and personal data because nobody decided what should be recorded.
156
+ - Migrating storage means rewriting the data model.
157
+
158
+ OpenAuditModel answers those questions once, in a way that is checkable by a validator.
159
+
160
+ It standardizes how applications describe operations such as authentication, authorization, identity
161
+ changes, privileged operations, data access, data modification, configuration changes, workflow
162
+ approvals, delegation, impersonation, administrative actions, security-relevant actions, external
163
+ data sharing, resource lifecycle operations, and deployment and operational changes.
164
+
165
+ ## Why is an audit event different from an application debug log?
166
+
167
+ An application log line says:
168
+
169
+ ```text
170
+ Document downloaded successfully.
171
+ ```
172
+
173
+ That is enough to debug the download and almost useless six months later. A structured audit event
174
+ answers the questions a reviewer, an investigator or a customer will actually ask:
175
+
176
+ - Who performed the operation?
177
+ - Was it a user, a service or the system itself?
178
+ - Was it performed on behalf of someone else?
179
+ - What action was performed?
180
+ - What resource was affected?
181
+ - What was the result?
182
+ - What authorization decision allowed or denied it?
183
+ - Was approval involved, and by whom?
184
+ - What changed?
185
+ - Which application produced the event, in which environment?
186
+ - How does it correlate with a request or a distributed trace?
187
+ - Does it contain personal data?
188
+ - Can its integrity be checked?
189
+
190
+ An audit event is written for a reader who was not there, does not have the source code, and is
191
+ reading it years later. A debug log is written for the engineer looking at it today. Both are useful;
192
+ they are not the same artifact, and one does not substitute for the other.
193
+
194
+ **Not every audit event needs every optional field.** A conforming event can be seven fields long.
195
+ See [examples/valid/minimal-event.json](examples/valid/minimal-event.json).
196
+
197
+ ## What does OpenAuditModel _not_ do?
198
+
199
+ It is **not**:
200
+
201
+ a log storage backend · a database · a SIEM · a GRC platform · a dashboard · an audit management
202
+ application · a compliance certification product · a policy engine · an authorization system · a
203
+ telemetry transport · a guarantee of regulatory or legal compliance.
204
+
205
+ It is **not a replacement for** OpenTelemetry, CloudEvents, ECS, OCSF, CADF or OSCAL.
206
+
207
+ The specification defines no web server, no REST API, no database, no user interface, no SaaS
208
+ service, no production SDK, no regulatory mapping packs, no country-specific fields and no
209
+ product-specific fields. That is by design, not by omission. The repository ships one optional
210
+ component — an MCP server that exposes the conformance engines to AI agents — which stores nothing
211
+ and is not required to produce or consume a conforming event.
212
+
213
+ ## How does it relate to existing standards?
214
+
215
+ OpenAuditModel complements existing standards rather than reinventing them. None of them is required.
216
+
217
+ | Standard | Relationship |
218
+ | ----------------------------- | -------------------------------------------------------------------------------- |
219
+ | **CloudEvents** | MAY be used as a transport envelope. The complete audit event travels as `data`. |
220
+ | **OpenTelemetry** | MAY be used for telemetry transport, collection and trace correlation. |
221
+ | **ECS** | Supported through an informative export mapping. |
222
+ | **OCSF** | Supported through an informative security-event mapping. |
223
+ | **CADF** | A conceptual reference for audit semantics. |
224
+ | **OSCAL** | May later be used for control and assessment mappings. Not addressed in v0.1. |
225
+ | **JSON Schema Draft 2020-12** | Defines the canonical machine-verifiable structure. |
226
+
227
+ ### Specifically, OpenTelemetry
228
+
229
+ OpenTelemetry is an excellent way to **transport and correlate** audit events, and
230
+ `request.traceId` / `request.spanId` are defined to be W3C Trace Context compatible so that an audit
231
+ event joins cleanly to the trace of the request that caused it.
232
+
233
+ OpenTelemetry does not define semantics for approval, delegation, business justification, before and
234
+ after change state, evidence references or per-event tamper-evidence. This project does not claim
235
+ that it does, and does not attempt to replace it. One caution: telemetry pipelines sample, and audit
236
+ trails must not be sampled. See [mappings/opentelemetry.md](mappings/opentelemetry.md).
237
+
238
+ ### Specifically, CloudEvents
239
+
240
+ CloudEvents standardizes the envelope; OpenAuditModel standardizes what is inside it. They compose
241
+ because neither tries to do the other's job. Using CloudEvents is optional, and an OpenAuditModel
242
+ event is valid standalone. See [mappings/cloudevents.md](mappings/cloudevents.md).
243
+
244
+ ### Specifically, ECS, OCSF and CADF
245
+
246
+ ECS is a field vocabulary for search; OCSF is a schema for security telemetry; CADF is a conceptual
247
+ model for cloud audit. OpenAuditModel exports to the first two and takes conceptual framing from the
248
+ third. All three mappings are informative, one-directional, and honest about what does not map — see
249
+ [mappings/](mappings/).
250
+
251
+ ## Why is the model backend-independent?
252
+
253
+ Because audit tooling normally arrives attached to a backend, and the model then acquires fields that
254
+ exist for that backend's benefit. Adopting the model means adopting the product, data cannot move
255
+ between stores without translation, and an audit trail that must be readable in a decade depends on a
256
+ product that may not exist then.
257
+
258
+ OpenAuditModel therefore defines no transport, no storage concept and no required pipeline.
259
+ Schema identifiers never have to be dereferenced, so validation needs no network call and no domain
260
+ registration has to be maintained for the schema to keep working. An event is equally valid written
261
+ to a file, inserted into a table, published to a topic or held in memory.
262
+
263
+ See [ADR 0003](decisions/0003-backend-and-transport-independence.md).
264
+
265
+ ## What is in v0.1?
266
+
267
+ ```text
268
+ specification/ 15 normative documents defining the model
269
+ schemas/v0.1/ the canonical JSON Schema (Draft 2020-12)
270
+ semantic-conventions/ recommended event names and vocabularies
271
+ profiles/ ten enforceable domain profiles, 127 rules
272
+ mappings/ informative mappings to CloudEvents, OTel, ECS, OCSF, CADF
273
+ examples/ 11 valid and 7 invalid conformance fixtures
274
+ examples/integrity/ generated tamper-evidence fixtures, valid and invalid
275
+ examples/privacy/ clean and finding fixtures for the privacy linter
276
+ examples/profiles/ conforming, violating and out-of-scope profile fixtures
277
+ conformance/ the `auditmodel` CLI and its test suite
278
+ mcp/ the remote MCP server, distributed as a container image
279
+ deploy/ Docker Compose and reverse-proxy examples
280
+ decisions/ 11 architecture decision records
281
+ ```
282
+
283
+ The core model requires seven fields — `specVersion`, `id`, `time`, `event`, `actor`, `resource`,
284
+ `application` — and offers nineteen optional context objects covering subject, delegation,
285
+ authentication, authorization, approval, request correlation, change, reason, evidence, integrity,
286
+ privacy, control categories, metadata and extensions.
287
+
288
+ Start with [specification/overview.md](specification/overview.md), then
289
+ [specification/event-model.md](specification/event-model.md).
290
+
291
+ ## What is experimental?
292
+
293
+ All of it. Specifically, expect these to change:
294
+
295
+ - **The compatibility strategy.** `specVersion` is pinned to `"0.1"` by a `const` in the schema.
296
+ How future versions negotiate compatibility is undecided.
297
+ - **Closed vocabularies.** `actor.type` and `event.severity` are closed enumerations in v0.1.
298
+ Whether they should be is an open question.
299
+ - **Array bounds and length limits.** The current values are conservative defaults, not researched
300
+ ones.
301
+ - **Profiles.** Ten are implemented and enforceable, but none has production adoption evidence yet:
302
+ their requirements are reasoned, not validated against real deployments.
303
+ - **Planned commands.** `check-coverage` is documented as future work and does not exist.
304
+ - **The profile rule vocabulary.** Six capabilities and one conditional operator. Profile inheritance,
305
+ composition and multi-profile checking are not implemented.
306
+ - **Privacy rule thresholds and vocabularies.** Hard-coded in v0.1, with no configuration and no
307
+ suppression mechanism.
308
+ - **The digest exclusion set.** Now fixed, so any future change to it invalidates every stored digest
309
+ rather than only new events.
310
+
311
+ Two things are already committed to, even while experimental: event names do not silently change
312
+ meaning, and extensions never weaken the core.
313
+
314
+ ## How can an event be validated?
315
+
316
+ Requires Node.js 22 or newer. Everything runs offline.
317
+
318
+ ```bash
319
+ npm install
320
+ npm run build
321
+
322
+ npm run auditmodel -- validate examples/valid/minimal-event.json
323
+ ```
324
+
325
+ After building, the CLI can also be run directly:
326
+
327
+ ```bash
328
+ node dist/conformance/src/cli.js validate examples/valid
329
+ auditmodel validate <event-file> # when installed or linked
330
+ ```
331
+
332
+ Output:
333
+
334
+ ```text
335
+ schema: https://openauditmodel.org/schemas/audit-event/0.1/schema.json (schemas/v0.1/audit-event.schema.json)
336
+
337
+ ok examples/valid/minimal-event.json
338
+
339
+ 1 event checked: 1 valid, 0 invalid, 0 unreadable
340
+ ```
341
+
342
+ A path may be a JSON file holding one event, a JSON file holding an array of events, a `.jsonl` or
343
+ `.ndjson` file holding one event per line, or a directory of those files.
344
+
345
+ A failure reports the JSON Pointer of every problem:
346
+
347
+ ```text
348
+ FAIL examples/invalid/delegation-without-subject.json
349
+ /subject missing required property "subject" [required]
350
+ ```
351
+
352
+ Exit codes: `0` valid, `1` at least one event failed validation, `2` usage error or a file that could
353
+ not be read or parsed.
354
+
355
+ Validation is one half of conformance. The rules a schema cannot express — do not record secrets, do
356
+ not misuse `subject` as a target, do not silently redefine an event name — are normative in the
357
+ specification and are not detectable by any validator. See
358
+ [specification/privacy.md](specification/privacy.md).
359
+
360
+ ## How is an event's integrity verified?
361
+
362
+ An event MAY carry `integrity` material: a digest of itself, and a link to the previous event in a
363
+ chain. Two commands check it, both entirely offline.
364
+
365
+ ```bash
366
+ auditmodel verify-integrity examples/integrity/valid/single-event-sha256.json
367
+ auditmodel verify-chain examples/integrity/valid/three-event-chain
368
+ ```
369
+
370
+ ```text
371
+ ok examples/integrity/valid/single-event-sha256.json
372
+ schema valid
373
+ canonicalization: RFC8785
374
+ hash algorithm: SHA-256
375
+ integrity hash valid
376
+ ```
377
+
378
+ ```text
379
+ chain chain-platform-control-service-instance-7c1a
380
+ events: 3
381
+ sequences: 1..3
382
+ ok all 3 event digests valid
383
+ ok all 2 previous-hash links valid
384
+ ok chain starts at a genesis event
385
+ ```
386
+
387
+ A failure names the finding and shows both digests, never the event:
388
+
389
+ ```text
390
+ FAIL examples/integrity/invalid/tampered-event.json
391
+ integrity hash mismatch [hash-mismatch]
392
+ declared: 03638029fc5fa4b1b043b762ab6c59b21ab8a60328a7a9956dcb8ccd9aac4e93
393
+ calculated: 193a462d707f0402e78cc172827e55579c80492905ca70647f4ce1c270f0706e
394
+ ```
395
+
396
+ **What is verified.** That the event validates against the canonical schema; that its declared
397
+ canonicalization and hash algorithm are ones the verifier implements; that recalculating its digest
398
+ reproduces `integrity.hash`; and, for chains, that every event links to its predecessor, that
399
+ sequences are unique and orderable, and that one algorithm is used throughout.
400
+
401
+ **What is not verified.** Signatures, keys and certificates — none of which v0.1 touches. Nor whether
402
+ the events you supplied are all the events that existed: chain verification proves consistency of the
403
+ supplied set, and an attacker who removes the _end_ of a chain leaves something internally consistent.
404
+ Detecting that needs an external checkpoint, which is out of scope.
405
+
406
+ **How the digest is calculated.** Deep-clone the event, remove exactly `/integrity/hash` and
407
+ `/integrity/signature`, serialize with **RFC 8785** (the JSON Canonicalization Scheme), encode as
408
+ UTF-8, hash, and encode as lower-case hexadecimal. RFC 8785 is used because a digest over JSON is
409
+ meaningless unless property order, number formatting and escaping are fixed first.
410
+
411
+ Everything else is _inside_ the digest — including `sequence`, `previousHash`, `chainId`, `batchId`,
412
+ `hashAlgorithm` and `canonicalization`. That is deliberate: if chain metadata were excluded, an
413
+ attacker could re-link and re-order events freely while every hash still verified.
414
+
415
+ **Supported algorithms.** `SHA-256`, `SHA-384` and `SHA-512`, matched case-sensitively. The schema
416
+ keeps the vocabulary open so a future algorithm needs no schema change — but acceptance by the schema
417
+ is not support, and an event declaring anything else is reported as unverifiable rather than verified.
418
+
419
+ **Tamper-evident, not tamper-proof.** Verification detects modification of the events it is given. It
420
+ does not prevent deletion, does not provide storage immutability, and creates no legal evidentiary
421
+ status. See [specification/integrity.md](specification/integrity.md) §8 and
422
+ [ADR 0006](decisions/0006-event-digest-and-chain-verification.md).
423
+
424
+ ## How are privacy risks detected?
425
+
426
+ Audit data concentrates who did what to whom, and instrumentation written once and rarely revisited
427
+ is exactly where a password ends up in a log that is kept for seven years.
428
+
429
+ ```bash
430
+ auditmodel lint-privacy examples/privacy/findings/access-token-field.json
431
+ auditmodel lint-privacy examples/privacy --format json
432
+ ```
433
+
434
+ ```text
435
+ FAIL examples/privacy/findings/access-token-field.json (1 finding)
436
+ CRITICAL OAM-PRIV-001 confidence high /metadata/accessToken
437
+ A property name associated with credentials carries a non-empty value.
438
+ recommendation: Remove the value. Record only the fact of the operation, or a
439
+ non-sensitive identifier for the credential.
440
+ ```
441
+
442
+ Seventeen rules across ten categories: credential-shaped property names, authorization header values,
443
+ private key markers, published token formats, URLs with embedded user information, evidence
444
+ references carrying query strings, connection strings, oversized values and raw payload fields — plus
445
+ one heuristic that measures character entropy.
446
+
447
+ **The output never contains the value that produced a finding.** Not a preview, not a prefix, not a
448
+ decoded token claim. Linter output ends up in CI logs and pull request comments, which are usually
449
+ less protected than the audit store; a tool that echoed its matches would move secrets from a
450
+ controlled system into an uncontrolled one, precisely when a secret was present.
451
+
452
+ **It runs entirely locally.** No remote service, no scanning API, no model, no network. It resolves
453
+ no reference, fetches no evidence URL and opens no file an event names. It never modifies or redacts
454
+ an event: the fix for a secret in an audit record is to change the instrumentation and rotate the
455
+ credential, not to rewrite history.
456
+
457
+ **A finding is a suspicion, and a clean result is not a clearance.** A finding does not establish a
458
+ breach, a regulatory violation or a confirmed credential. And a password that happens to be a
459
+ dictionary word, stored under a field named `note`, matches nothing — as does most personal data,
460
+ which is not shaped like a secret at all. Severity and confidence are reported separately for this
461
+ reason: a field named `password` is critical/high, a random-looking string in an arbitrary field is
462
+ medium/low.
463
+
464
+ Exit codes: `0` no findings, `1` findings or a schema-invalid event, `2` usage or input error. Full
465
+ rule catalogue, thresholds, inspected paths and honest limits:
466
+ [specification/privacy.md](specification/privacy.md) §6 and
467
+ [ADR 0007](decisions/0007-deterministic-privacy-linting.md).
468
+
469
+ ## How do I use it from an AI agent?
470
+
471
+ A remote MCP server exposes the same deterministic engines the CLI uses, so an agent writing
472
+ instrumentation can validate, privacy-lint and profile-check an event without cloning anything.
473
+
474
+ ```bash
475
+ claude mcp add --transport http openauditmodel https://mcp.openauditmodel.org/mcp
476
+ ```
477
+
478
+ Seven tools — `validate_event`, `verify_integrity`, `verify_chain`, `lint_privacy`,
479
+ `check_profile`, `generate_event_template`, `get_event_guidance` — three prompts, and twenty-nine
480
+ read-only resources covering the specification, both schemas, the semantic conventions and the IAM
481
+ profile.
482
+
483
+ **It is a remote service, and this matters.** MCP tool inputs are processed ephemerally by the
484
+ OpenAuditModel MCP service. The service does not intentionally persist audit event content or
485
+ include tool arguments in application logs. Users should review their organization’s data-handling
486
+ requirements before submitting production audit events to a remote MCP service. Nothing here claims
487
+ your events stay on your machine — they do not. For regulated audit data, use the CLI, which sends
488
+ nothing anywhere.
489
+
490
+ No model runs inside the server: every tool is deterministic and read-only, and the prompts return
491
+ guidance text for your agent to act on. Findings never carry the value that produced them.
492
+
493
+ > Deployed and verified: `https://mcp.openauditmodel.org/mcp` answers, and the site above serves the
494
+ > canonical schemas. Both are still an unauthenticated v0.1 alpha — see
495
+ > [mcp/README.md](mcp/README.md), "Public alpha risk".
496
+
497
+ Run it yourself — which keeps your audit events inside your own network. The image is built from this
498
+ repository; there is no registry to pull from:
499
+
500
+ ```bash
501
+ docker build --tag openauditmodel-mcp:local --file Dockerfile .
502
+
503
+ docker run --rm -p 127.0.0.1:3000:3000 \
504
+ -e OAM_ALLOWED_ORIGINS=https://openauditmodel.org \
505
+ openauditmodel-mcp:local
506
+ ```
507
+
508
+ See [mcp/README.md](mcp/README.md), [deploy/README.md](deploy/README.md) and
509
+ [ADR 0011](decisions/0011-self-hosted-docker-mcp-server.md).
510
+
511
+ ## How are extensions added?
512
+
513
+ Two extension points, for two different purposes.
514
+
515
+ **`metadata`** carries domain-specific audit interpretation data with plain keys:
516
+
517
+ ```json
518
+ { "metadata": { "assignedRole": "support-agent", "expiresAt": "2026-06-16T00:00:00Z" } }
519
+ ```
520
+
521
+ **`extensions`** carries vendor-specific or product-specific data under a reverse-domain namespace of
522
+ at least three segments, which the validator enforces:
523
+
524
+ ```json
525
+ {
526
+ "extensions": {
527
+ "com.example.identity.directory.id": "directory-1",
528
+ "io.vendor.product.feature.enabled": true
529
+ }
530
+ }
531
+ ```
532
+
533
+ Keys like `clusterId` or `customValue` are rejected. Extensions must never weaken a required core
534
+ field or change the meaning of an existing one, and consumers must ignore extensions they do not
535
+ understand. See [specification/extension-model.md](specification/extension-model.md) and
536
+ [ADR 0004](decisions/0004-reverse-domain-extension-namespaces.md).
537
+
538
+ ## What are profiles, and how are they checked?
539
+
540
+ A profile is an optional, stricter set of requirements for one domain. The core says every event
541
+ needs an actor; a profile says that _in this domain_, a privileged role assignment also needs an
542
+ approval and a multi-factor authenticated session.
543
+
544
+ ```bash
545
+ auditmodel check-profile examples/profiles/identity-and-access-management/valid \
546
+ --profile identity-and-access-management
547
+ ```
548
+
549
+ ```text
550
+ ok .../valid/role-assign-privileged.json (IAM-CORE-001, IAM-CORE-002, IAM-ROLE-001, IAM-ROLE-002)
551
+
552
+ FAIL .../invalid/privileged-role-without-mfa.json (1 violation)
553
+ ERROR IAM-ROLE-002 /authentication/mfa
554
+ required by the profile to equal true
555
+ ```
556
+
557
+ **A profile only ever adds.** Every profile-conforming event is a core-conforming event, and this is
558
+ enforced structurally rather than by review: the rule vocabulary contains no keyword that could remove
559
+ a requirement — no `optionalPaths`, no `exemptPaths`, no `overrides` — and core validation runs first,
560
+ so an event failing the core schema is reported as core-invalid with its profile rules never
561
+ evaluated.
562
+
563
+ **Profiles are data, not code.** A profile is a JSON document validated against
564
+ [profile-definition.schema.json](profiles/profile-definition.schema.json). Six capabilities: two
565
+ selector forms, three requirement forms, one recommendation form, and one conditional — a single path
566
+ compared for equality against a single scalar. No expressions, no scripts, no regular expressions,
567
+ nothing executed. The eleven-rule identity profile is expressed entirely in JSON, with no TypeScript.
568
+ Adding a profile requires no code.
569
+
570
+ **An event no rule governs is `not-applicable`, never conforming**, and exits `3`. Silence is not
571
+ conformance: a pipeline checking document events against an identity profile must not read a pass as
572
+ assurance.
573
+
574
+ | Profile | Status |
575
+ | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
576
+ | [Identity and access management](profiles/identity-and-access-management/) | **Implemented** — 11 rules across accounts, roles, permissions, service accounts and credential rotation |
577
+ | [Document management](profiles/document-management/) | **Implemented** — 11 rules across sharing, permissions, versioning, retention and legal hold |
578
+ | [Incident management](profiles/incident-management/) | **Implemented** — 15 rules across incident, problem and corrective-action lifecycle |
579
+ | [Message broker management](profiles/message-broker-management/) | **Implemented** — 12 rules across broker control-plane, ACL, quota, configuration and offset administration |
580
+ | [Deployment and change management](profiles/deployment-and-change-management/) | **Implemented** — 13 rules across deployment, release, rollback and configuration change |
581
+ | [Financial transaction management](profiles/financial-transaction-management/) | **Implemented** — 12 rules across transfers, payments, reversals, settlement and limits |
582
+ | [Secrets and key management](profiles/secrets-and-key-management/) | **Implemented** — 14 rules across secret, key and certificate lifecycle and high-risk access |
583
+ | [Customer and account management](profiles/customer-and-account-management/) | **Implemented** — 13 rules across customer and business-account lifecycle |
584
+ | [Backup and recovery](profiles/backup-and-recovery/) | **Implemented** — 13 rules across backup, restore, recovery and failover |
585
+ | [API and integration management](profiles/api-and-integration-management/) | **Implemented** — 13 rules across API credential, webhook and integration lifecycle |
586
+
587
+ **Profiles are not regulatory mappings**, and profile conformance is not legal compliance. A profile
588
+ requires audit fields; it cites no regulation, article or jurisdiction, and the definition format
589
+ gives it nowhere to put one. Profiles also do not replace privacy linting: a profile says which fields
590
+ must be present, the linter says which values must not, and every published profile fixture is
591
+ required by test to pass both.
592
+
593
+ See [profiles/README.md](profiles/README.md), [ADR 0005](decisions/0005-core-and-profile-separation.md)
594
+ and [ADR 0008](decisions/0008-declarative-profile-conformance.md).
595
+
596
+ ## Legal and compliance limitations
597
+
598
+ Read this section before citing OpenAuditModel in any compliance context.
599
+
600
+ 1. **No compliance guarantee.** Conformance is a statement about the shape and semantics of data. It
601
+ is not compliance with GDPR, HIPAA, SOC 2, ISO 27001, PCI DSS, or any other framework, and must not
602
+ be presented as such.
603
+ 2. **No legal advice.** Nothing in this repository is legal advice.
604
+ 3. **No regulatory mappings.** The core model contains no regulation identifiers, article numbers,
605
+ control identifiers or jurisdiction-specific fields, deliberately. `controlCategories` carries
606
+ regulation-neutral labels only.
607
+ 4. **No evidentiary status.** A hash or signature does not automatically make an audit record
608
+ admissible or probative. That depends on jurisdiction, process and key custody.
609
+ 5. **Tamper-evident, not immutable.** Integrity metadata makes alteration detectable. It does not
610
+ prevent deletion, does not provide storage immutability and does not replace write-once storage.
611
+ See [specification/integrity.md](specification/integrity.md).
612
+ 6. **Validation cannot detect secrets.** A password in `metadata` passes every test in this
613
+ repository. See [specification/privacy.md](specification/privacy.md).
614
+ 7. **Experimental.** The model may change incompatibly before 1.0.
615
+
616
+ ## Contributing
617
+
618
+ Contributions are welcome, including disagreement with the decisions recorded in
619
+ [decisions/](decisions/).
620
+
621
+ Specification changes follow an RFC-like process: open an issue using the specification change
622
+ template, describing the problem, the proposed change, the compatibility impact and the conformance
623
+ tests that would prove it. [CONTRIBUTING.md](CONTRIBUTING.md) explains how to propose a core field, a
624
+ semantic convention, a domain profile, an external mapping or a vendor extension, and how
625
+ compatibility is evaluated.
626
+
627
+ Please also read [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Security issues go to
628
+ [SECURITY.md](SECURITY.md), not to the public tracker.
629
+
630
+ ## Licensing
631
+
632
+ All content in this repository — specification text, JSON Schemas, examples, tooling and tests — is
633
+ licensed under the **Apache License 2.0**. See [LICENSE](LICENSE).
634
+
635
+ A single license was chosen deliberately for v0.1. Splitting documentation under a separate content
636
+ license such as CC BY 4.0 is a reasonable thing for a standards project to do, and adds a licensing
637
+ boundary that contributors have to reason about on every change. If the project's governance later
638
+ justifies that boundary, the change will be recorded as an architecture decision. Until then, one
639
+ license applies to everything.
640
+
641
+ This repository contains no copyrighted control framework text, no proprietary framework content, no
642
+ licensed regulatory commentary and no vendor documentation.
643
+
644
+ ## Status of this repository
645
+
646
+ Version 0.1 is a specification, a canonical schema and a conformance toolchain. It is experimental,
647
+ not production-ready, and carries no compliance guarantee.
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ export declare const EXIT_OK = 0;
3
+ export declare const EXIT_INVALID = 1;
4
+ export declare const EXIT_ERROR = 2;
5
+ /**
6
+ * No verdict was produced for the input.
7
+ *
8
+ * `check-profile`: nothing checked was governed by the profile.
9
+ * `lint-privacy`: the input could not be evaluated as an audit event, so it was
10
+ * not scanned at all.
11
+ *
12
+ * Distinct from 0 in both cases for the same reason: a pipeline must not be able
13
+ * to read "the tool said nothing" as "the tool was satisfied". Distinct from 1
14
+ * because 1 means a real verdict was produced and it was negative, which is a
15
+ * different thing for an operator to act on.
16
+ */
17
+ export declare const EXIT_NO_VERDICT = 3;
18
+ /** Retained name for the `check-profile` reading of {@link EXIT_NO_VERDICT}. */
19
+ export declare const EXIT_NOT_APPLICABLE = 3;
20
+ export declare function run(argv: readonly string[]): number;