@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
@@ -0,0 +1,271 @@
1
+ # API and Integration Management Profile
2
+
3
+ **Status: Experimental. Implemented in v0.1, 13 rules, 11 of them enforceable.**
4
+
5
+ The enforceable rules are in [profile.json](profile.json).
6
+
7
+ ```bash
8
+ auditmodel check-profile examples/profiles/api-and-integration-management/valid --profile api-and-integration-management
9
+ ```
10
+
11
+ ## Purpose
12
+
13
+ An integration is a standing hole in a trust boundary. Somebody opened it, somebody widened it, and
14
+ one day somebody will ask when. The events that answer that question — an API key issued, a webhook
15
+ pointed somewhere new, a connector authorized against a third party — are administrative events that
16
+ happen rarely and matter enormously, and they are routinely the thinnest events a platform emits,
17
+ because the engineering attention goes to the traffic rather than to the administration of it.
18
+
19
+ This profile states what those administrative events have to carry: who decided, how they were
20
+ authenticated, which integration point changed, which external party is on the other side, why a
21
+ withdrawal happened, and what changed in a reconfiguration. It states equally clearly what must never
22
+ appear in them — the key, the token, the client secret, the signing secret, the callback URL.
23
+
24
+ ## Scope
25
+
26
+ Applications that issue and manage API credentials, that let an operator subscribe an external
27
+ destination to their events, or that connect to third-party systems: SaaS platforms, integration
28
+ platforms, developer portals, API gateways with a management plane, and any product with an
29
+ "Integrations" or "Developer" settings page.
30
+
31
+ The profile is vendor-neutral. It describes operations any integration surface performs. It assumes
32
+ no particular authorization protocol, no particular transport, no particular hosting model, and no
33
+ particular approval workflow. It does not require `/request/protocol`, `/request/traceId` or any
34
+ other field that presupposes a specific technology stack.
35
+
36
+ ## Event families
37
+
38
+ | Family | Events | Governed |
39
+ | ------------------------ | --------------------------------------------------------------------------- | -------- |
40
+ | API credential lifecycle | `api-key.create`, `.rotate`, `.revoke`, `.delete` | yes |
41
+ | Webhook administration | `webhook.create`, `.update`, `.enable`, `.disable`, `.delete`, `.test` | yes |
42
+ | Integration lifecycle | `integration.connect`, `.disconnect`, `.enable`, `.disable`, `.reauthorize` | yes |
43
+ | Integration settings | `integration.configuration.*` | yes |
44
+ | Sync administration | `integration.sync.start`, `integration.sync.cancel` | yes |
45
+ | API traffic | `api.request` and every other data-plane request | **no** |
46
+ | Webhook delivery | `webhook.delivery.*` | **no** |
47
+ | Sync execution | `integration.sync.progress` and other per-page or polling events | **no** |
48
+
49
+ ## Explicit exclusions
50
+
51
+ **The data plane is not governed, and cannot be governed by accident.**
52
+
53
+ An API gateway emits one event per request. A webhook dispatcher emits one per delivery attempt, and
54
+ retries multiply it. A polling connector emits one per page for as long as it runs. Requiring an
55
+ authorization decision, an integration classification and a justification on each of those would put
56
+ the profile's cost squarely on the highest-volume events in the system, in exchange for almost no
57
+ review value — and the requirement would be switched off rather than met.
58
+
59
+ The exclusion is **structural**, not a matter of discipline. Every selector in this profile is either
60
+ an exact event name or the single narrow prefix `integration.configuration.`. **No selector uses a
61
+ bare `api.`, `api-key.`, `webhook.` or `integration.` prefix**, so `api.request`,
62
+ `webhook.delivery.attempt`, `integration.sync.progress` and `api-key.verify` match no rule at all and
63
+ `check-profile` reports them as not applicable. Three fixtures and a test hold that boundary in
64
+ place, because widening one prefix later would silently start governing every request, delivery and
65
+ poll in a deployment.
66
+
67
+ Excluded does not mean unaudited. A delivery attempt is still a conforming OpenAuditModel event;
68
+ [data-access.md](../../semantic-conventions/data-access.md) and
69
+ [correlation-and-tracing.md](../../semantic-conventions/correlation-and-tracing.md) cover recording
70
+ traffic. This profile simply makes no additional demands of it.
71
+
72
+ ## Rules
73
+
74
+ | Rule | Applies to | Requires |
75
+ | ------------------------ | ----------------------------------------------------------------------- | ------------------------------------------------------------------------ |
76
+ | `INTEGRATION-CORE-001` | every governed event | `/authorization`, `/metadata/integration/type` |
77
+ | `INTEGRATION-CORE-002` | every governed event | _recommends_ `/reason`, `/approval`, correlation ID, provider |
78
+ | `INTEGRATION-CORE-003` | every governed event where the producer declared approval was required | `/approval`, `/approval/status` |
79
+ | `INTEGRATION-AUTHN-001` | credential and connection operations where `actor.type` is `user` | `/authentication` |
80
+ | `INTEGRATION-AUTHN-002` | the same operations where `actor.type` is `admin` | `/authentication` |
81
+ | `INTEGRATION-KEY-001` | `api-key.create`, `.rotate`, `.revoke`, `.delete` | `/metadata/integration/credentialReference` |
82
+ | `INTEGRATION-KEY-002` | `api-key.create`, `api-key.rotate` | _recommends_ scope, expiry, `/resource/ownerId` |
83
+ | `INTEGRATION-REVOKE-001` | revocations, deletions, disablements, disconnections, sync cancellation | `/reason` |
84
+ | `INTEGRATION-HOOK-001` | every webhook administration event | `/metadata/integration/webhookId`, `/metadata/integration/endpointClass` |
85
+ | `INTEGRATION-CONFIG-001` | `webhook.update`, `integration.configuration.*` | `/change`; recommends `/change/changedFields` |
86
+ | `INTEGRATION-CONN-001` | every `integration.*` governed event | `/metadata/integration/connectionId`, `/metadata/integration/provider` |
87
+ | `INTEGRATION-FLOW-001` | `integration.connect`, `.reauthorize`, `sync.start`, `sync.cancel` | `/request/correlationId` |
88
+ | `INTEGRATION-FAIL-001` | every governed event whose outcome is `failure` | `/event/error/type` |
89
+
90
+ `INTEGRATION-CORE-002` and `INTEGRATION-KEY-002` are `warning` rules: they never fail conformance.
91
+ The other eleven are `error` rules. Each rule's full text and rationale is in
92
+ [profile.json](profile.json).
93
+
94
+ ## Metadata namespace
95
+
96
+ Everything this profile requires under `metadata` lives at `/metadata/integration/`. Namespacing is
97
+ not decoration: an event can be governed by two profiles at once, and `type`, `provider` and
98
+ `expiresAt` mean different things to a secrets profile and to this one. A root key such as
99
+ `/metadata/type` would be a collision waiting to happen.
100
+
101
+ | Field | Type | Meaning |
102
+ | --------------------- | ------- | ----------------------------------------------------------------------------------------------------------- |
103
+ | `type` | string | Kind of integration point: `api-credential`, `outbound-webhook`, `service-connector`, `data-sync-connector` |
104
+ | `provider` | string | Stable logical name of the party on the other side. Never a URL, never a legal entity name |
105
+ | `connectionId` | string | Identifier of the connection instance |
106
+ | `webhookId` | string | Identifier of the webhook subscription |
107
+ | `credentialReference` | string | Non-secret handle for the credential material. **Never the credential** |
108
+ | `endpointClass` | string | Where a callback goes: `internal-service`, `partner-network`, `public-internet`, `unknown` |
109
+ | `approvalRequired` | boolean | Producer's declaration that local policy required approval for this change |
110
+ | `scope` | string | What a credential may do. Recommended at issuance |
111
+ | `expiresAt` | string | When a credential stops working. Recommended at issuance |
112
+
113
+ All vocabularies above are open. The profile requires that the field be present and be a string; it
114
+ never says which strings are legal, because an integration taxonomy is an operator's decision.
115
+
116
+ The table lists only the fields some rule requires or recommends. The namespace is not closed:
117
+ producers may add their own descriptive fields beside them, and the fixtures do — `eventSelection` on
118
+ a webhook, `syncMode` on a sync, `deliveryAttempt` and `pageNumber` on the ungoverned data-plane
119
+ examples. No rule constrains those, and none is needed for conformance.
120
+
121
+ ## Conditional-policy fields
122
+
123
+ Four rules are conditional, and each fires on exactly one producer-set discriminator:
124
+
125
+ | Rule | Fires when |
126
+ | ----------------------- | -------------------------------------------------- |
127
+ | `INTEGRATION-CORE-003` | `/metadata/integration/approvalRequired` is `true` |
128
+ | `INTEGRATION-AUTHN-001` | `/actor/type` is `"user"` |
129
+ | `INTEGRATION-AUTHN-002` | `/actor/type` is `"admin"` |
130
+ | `INTEGRATION-FAIL-001` | `/event/outcome` is `"failure"` |
131
+
132
+ When a condition's path is absent, the condition does not hold and the rule contributes nothing. That
133
+ is the engine's defined behaviour and it is the reason `approvalRequired` should be recorded as
134
+ `false` rather than omitted: `false` is an answer, absence is silence.
135
+
136
+ ## Approval model
137
+
138
+ **Approval is never universally required.** Whether an outbound webhook needs two sign-offs or none
139
+ is an operator's policy, not this specification's, and a profile that demanded approval for every
140
+ integration change would describe one organization's process and be ignored everywhere else.
141
+
142
+ The profile therefore takes the producer's word for it. `INTEGRATION-CORE-002` recommends `/approval`
143
+ on every governed event, so its absence is visible as a warning. `INTEGRATION-CORE-003` requires
144
+ `/approval` and `/approval/status` **only** when the producer has set
145
+ `/metadata/integration/approvalRequired` to `true` — the same shape as the IAM profile's privileged
146
+ flag and the document profile's external-share flag.
147
+
148
+ Recording `approval: { "status": "not-required" }` is the recommended way to say that no approval was
149
+ needed. It answers the reviewer's question instead of leaving a hole where the answer would be. Most
150
+ of the valid fixtures do exactly that.
151
+
152
+ ## Privacy considerations
153
+
154
+ This is the profile with the highest chance of a producer putting a live credential in an audit
155
+ event, because the events are _about_ credentials. The following MUST NOT appear anywhere in an
156
+ event, in `metadata`, `extensions`, `change.before`, `change.after`, `resource.attributes`,
157
+ `event.summary`, `event.error.message` or `reason.text`:
158
+
159
+ - API key values, at issuance or rotation
160
+ - OAuth access tokens, refresh tokens, authorization codes and client secrets
161
+ - Webhook signing secrets and HMAC keys
162
+ - `Authorization` header values of any kind
163
+ - Signed or tokenized callback URLs, and any URL carrying a query string
164
+
165
+ The profile is built so that a producer never needs to. `credentialReference` exists so there is a
166
+ non-sensitive place to put "which key"; `endpointClass` exists so there is a non-sensitive place to
167
+ put "where the data goes". Requiring the full callback URL would have been the obvious design, and it
168
+ would have turned every conforming audit trail into a credential store, because delivery URLs
169
+ routinely carry shared secrets in their path or query string.
170
+
171
+ `INTEGRATION-CONFIG-001` requires `/change` but neither requires nor recommends `/change/before` or
172
+ `/change/after`, for the same reason: integration configuration is full of endpoints and headers.
173
+ Changed field names are the recommended form of the answer.
174
+
175
+ Every fixture in this profile — valid, invalid and not-applicable alike — is required by test to
176
+ pass `auditmodel lint-privacy`, and CI runs the command over `valid/`. A profile that accepted an event
177
+ carrying a credential would be worse than no profile at all. See
178
+ [specification/privacy.md](../../specification/privacy.md).
179
+
180
+ ## Known rule-language limitations
181
+
182
+ The v0.1 rule language checks presence, JSON type and strict scalar equality against a single
183
+ condition. These are the places this profile wanted more and did not get it:
184
+
185
+ - **No disjunction.** `INTEGRATION-AUTHN-001` and `INTEGRATION-AUTHN-002` are the same requirement
186
+ written twice because `actor.type` cannot be compared against `"user"` **or** `"admin"` in one
187
+ rule. A deployment that models operators under some other core principal type is not caught.
188
+ - **No cross-field comparison.** The profile cannot assert that `approval.receivedApprovals` reaches
189
+ `approval.requiredApprovals`, nor that an event with `approvalRequired: true` does not carry
190
+ `approval.status: "not-required"` — a contradiction the engine cannot see.
191
+ - **No numeric ranges.** `requiredMetadata` checks that a value is a `number` or an `integer`. It
192
+ cannot require that an expiry is within a maximum lifetime, or that a retry count is bounded.
193
+ - **No string patterns.** The profile can require that `endpointClass` is a string. It cannot require
194
+ that it comes from a vocabulary, and it cannot reject a full URL stored there. The privacy linter
195
+ is the backstop for the second one, not the profile.
196
+ - **No array-content predicates.** `change.changedFields` cannot be required to contain a particular
197
+ entry, and `approval.approvers` cannot be required to exclude the actor — which is exactly how a
198
+ self-approval would be detected.
199
+ - **Absence and falsity are different, and only one is checkable.** A rule fires on
200
+ `approvalRequired: true` and stays silent on both `false` and absent. Recording `false` explicitly
201
+ is a documentation convention this profile recommends; it is not something the engine can compel.
202
+
203
+ Each of these is guidance in this README rather than a rule, deliberately. A profile that pretended
204
+ to enforce something it cannot check would be worse than one that says plainly where it stops.
205
+
206
+ ## Cross-profile overlaps
207
+
208
+ **`secrets-and-key-management`.** The overlap is real and it is intentional. That profile governs the
209
+ **security lifecycle of secret and key material** — how material is generated, stored, rotated,
210
+ escrowed and destroyed. This profile governs the **lifecycle and configuration of APIs, webhooks and
211
+ external integrations** — which integration points exist, who opened them, where they point and why
212
+ they were closed.
213
+
214
+ An API key rotation sits in the intersection, and it may legitimately conform to either profile
215
+ depending on the producer's vocabulary and which control domain the producer intends the event to
216
+ serve. The two profiles deliberately do **not** use identical selectors, and neither claims the
217
+ event exclusively. If a producer wants the event to satisfy both, it can: both profiles only add
218
+ requirements, both namespace their metadata, and nothing in one contradicts the other. Check against
219
+ both and the event must satisfy the union.
220
+
221
+ **`identity-and-access-management`.** `identity.credential.rotate` is the IAM profile's view of a
222
+ credential belonging to a **principal**. `api-key.rotate` here is the view of a credential belonging
223
+ to an **integration**. Producers whose API keys are modelled as principal credentials should use the
224
+ IAM vocabulary; producers whose keys are modelled as integration configuration should use this one.
225
+
226
+ **`deployment-and-change-management`.** An integration configuration change made through a pipeline
227
+ is both a configuration change and a deployment. `/change/deploymentId` is the core field that links
228
+ them; this profile requires neither profile's fields of the other.
229
+
230
+ ## Fixture matrix
231
+
232
+ [examples/profiles/api-and-integration-management/](../../examples/profiles/api-and-integration-management/)
233
+ — thirteen valid, twelve invalid, three not applicable.
234
+
235
+ | Kind | Count | Guarantee |
236
+ | ----------------- | ----- | ------------------------------------------------------------------------------- |
237
+ | `valid/` | 13 | Core-valid, privacy-clean, conforming (exit 0) |
238
+ | `invalid/` | 12 | Core-valid, privacy-clean, failing **exactly one** rule at a documented pointer |
239
+ | `not-applicable/` | 3 | Core-valid, privacy-clean, governed by no rule (exit 3) |
240
+
241
+ Every one of the eleven enforceable rules has at least one negative fixture;
242
+ `INTEGRATION-CORE-001` has two, one for each field it requires.
243
+
244
+ ## Not-applicable rationale
245
+
246
+ Three fixtures exist to prove an exclusion rather than to demonstrate a requirement:
247
+
248
+ - `api-request.json` — an ordinary authenticated API call. The busiest event in an API platform, and
249
+ one this profile says nothing about.
250
+ - `webhook-delivery.json` — a successful delivery attempt. Governing this would mean requiring an
251
+ authorization decision on every outbound HTTP call a dispatcher makes, including retries.
252
+ - `integration-sync-progress.json` — a per-page progress event from a polling connector. The sync
253
+ that _started_ is governed; the pages it walks are not.
254
+
255
+ All three are perfectly good OpenAuditModel events. `check-profile` reports each as not applicable
256
+ with exit code 3, which is deliberately **not** conformance: an event no rule governs is out of
257
+ scope, never silently approved.
258
+
259
+ ## Open questions
260
+
261
+ - Should `api-key.create` require an expiry rather than recommend one? The argument for is that a
262
+ non-expiring credential is the finding; the argument against is that many products legitimately
263
+ issue keys with no expiry and would fill the field with a sentinel.
264
+ - Is `endpointClass` the right abstraction, or should the profile require a destination host
265
+ identifier that a producer resolves internally? A class answers the risk question; a host
266
+ identifier answers the forensic one. There is no adoption evidence yet for either.
267
+ - Should `integration.sync.start` require the sync scope — full or incremental, and over what — as
268
+ `INTEGRATION-CONN-001` requires the connection? The vocabulary differs enough between connectors
269
+ that a required field would likely be filled inconsistently.
270
+ - Should a self-approval be expressible? Detecting one needs an array-content predicate the rule
271
+ language does not have.
@@ -0,0 +1,258 @@
1
+ {
2
+ "profileVersion": "0.1",
3
+ "name": "api-and-integration-management",
4
+ "version": "0.1",
5
+ "status": "experimental",
6
+ "coreVersions": ["0.1"],
7
+ "title": "API and Integration Management Profile",
8
+ "description": "Additional conformance requirements for the administration of API credentials, webhook subscriptions and third-party integrations: issuing and revoking API keys, creating and reconfiguring webhooks, connecting, reauthorizing and disconnecting external systems, and starting or cancelling integration syncs. Every requirement adds to the OpenAuditModel Core Specification; none relaxes it. Data-plane traffic is deliberately not governed: ordinary API requests, webhook deliveries and routine integration polling match no rule in this profile.",
9
+ "rules": [
10
+ {
11
+ "id": "INTEGRATION-CORE-001",
12
+ "description": "Every governed API, webhook or integration administration event records the authorization decision that permitted it and the kind of integration point it acted on.",
13
+ "rationale": "An integration is a standing hole in a trust boundary, and the events that open, widen or close one are the only record that the hole was opened deliberately. Without a recorded decision nothing distinguishes a change a policy allowed from one that bypassed policy, and without the kind of integration point a reviewer cannot tell whether the event concerns a credential someone holds, a callback that pushes data outward, or a connector that pulls data in — three operations with entirely different blast radii that otherwise look identical in a report.",
14
+ "severity": "error",
15
+ "events": [
16
+ "api-key.create",
17
+ "api-key.rotate",
18
+ "api-key.revoke",
19
+ "api-key.delete",
20
+ "webhook.create",
21
+ "webhook.update",
22
+ "webhook.enable",
23
+ "webhook.disable",
24
+ "webhook.delete",
25
+ "webhook.test",
26
+ "integration.connect",
27
+ "integration.disconnect",
28
+ "integration.enable",
29
+ "integration.disable",
30
+ "integration.reauthorize",
31
+ "integration.sync.start",
32
+ "integration.sync.cancel"
33
+ ],
34
+ "eventPrefixes": ["integration.configuration."],
35
+ "requiredPaths": ["/authorization"],
36
+ "requiredMetadata": [{ "path": "/integration/type", "type": "string" }]
37
+ },
38
+ {
39
+ "id": "INTEGRATION-CORE-002",
40
+ "description": "A governed integration event should record why it happened, whether it was approved, which external party it concerns, and how it correlates with the wider operation.",
41
+ "rationale": "These are the fields a reviewer reaches for first and a producer omits most often. They are recommended rather than required because a scheduled rotation has no business justification beyond the schedule, because most integration changes legitimately need no approval, and because a first-party API key has no external counterparty to name. A missing one of these should prompt a question, not fail a build.",
42
+ "severity": "warning",
43
+ "events": [
44
+ "api-key.create",
45
+ "api-key.rotate",
46
+ "api-key.revoke",
47
+ "api-key.delete",
48
+ "webhook.create",
49
+ "webhook.update",
50
+ "webhook.enable",
51
+ "webhook.disable",
52
+ "webhook.delete",
53
+ "webhook.test",
54
+ "integration.connect",
55
+ "integration.disconnect",
56
+ "integration.enable",
57
+ "integration.disable",
58
+ "integration.reauthorize",
59
+ "integration.sync.start",
60
+ "integration.sync.cancel"
61
+ ],
62
+ "eventPrefixes": ["integration.configuration."],
63
+ "recommendedPaths": [
64
+ "/reason",
65
+ "/approval",
66
+ "/request/correlationId",
67
+ "/metadata/integration/provider"
68
+ ]
69
+ },
70
+ {
71
+ "id": "INTEGRATION-CORE-003",
72
+ "description": "When the producer declares that local policy required approval for this change, the event carries the approval and its state.",
73
+ "rationale": "Approval requirements for integration changes are set by the operator, not by this specification: one organization gates every outbound webhook, another gates none. So the profile does not guess. When a producer has declared that approval was required, an event that omits the approval record is evidence that the control was skipped or that the trail cannot show it was honoured — and those two are indistinguishable after the fact, which is exactly the ambiguity an audit trail exists to remove. The status is required alongside the object because an approval with no state answers nothing.",
74
+ "severity": "error",
75
+ "events": [
76
+ "api-key.create",
77
+ "api-key.rotate",
78
+ "api-key.revoke",
79
+ "api-key.delete",
80
+ "webhook.create",
81
+ "webhook.update",
82
+ "webhook.enable",
83
+ "webhook.disable",
84
+ "webhook.delete",
85
+ "webhook.test",
86
+ "integration.connect",
87
+ "integration.disconnect",
88
+ "integration.enable",
89
+ "integration.disable",
90
+ "integration.reauthorize",
91
+ "integration.sync.start",
92
+ "integration.sync.cancel"
93
+ ],
94
+ "eventPrefixes": ["integration.configuration."],
95
+ "when": { "path": "/metadata/integration/approvalRequired", "equals": true },
96
+ "requiredPaths": ["/approval", "/approval/status"]
97
+ },
98
+ {
99
+ "id": "INTEGRATION-AUTHN-001",
100
+ "description": "A credential or connection-authorization operation performed by a human user records how that user was authenticated.",
101
+ "rationale": "Issuing, rotating or revoking an API key and authorizing or tearing down a connection are the operations an attacker performs to establish or remove persistence. When a person did it, the strength of the session behind the action is the fact that separates a routine administrative change from a change made through a stolen cookie, and it can never be reconstructed later. The requirement is conditional on the actor being a person because a scheduled rotation worker has no interactive session to describe, and a rule that demanded one would be switched off rather than met.",
102
+ "severity": "error",
103
+ "events": [
104
+ "api-key.create",
105
+ "api-key.rotate",
106
+ "api-key.revoke",
107
+ "api-key.delete",
108
+ "integration.connect",
109
+ "integration.reauthorize",
110
+ "integration.disconnect"
111
+ ],
112
+ "when": { "path": "/actor/type", "equals": "user" },
113
+ "requiredPaths": ["/authentication"]
114
+ },
115
+ {
116
+ "id": "INTEGRATION-AUTHN-002",
117
+ "description": "A credential or connection-authorization operation performed by an administrator records how that administrator was authenticated.",
118
+ "rationale": "This states the same requirement as INTEGRATION-AUTHN-001 for the other human principal type in the core model. It exists as a separate rule because the v0.1 rule language permits exactly one equality condition per rule and has no disjunction, so `user` and `admin` cannot be expressed in one condition. Splitting the rule is the honest way to cover both; collapsing them by dropping the condition would demand an interactive session from every automated rotation worker.",
119
+ "severity": "error",
120
+ "events": [
121
+ "api-key.create",
122
+ "api-key.rotate",
123
+ "api-key.revoke",
124
+ "api-key.delete",
125
+ "integration.connect",
126
+ "integration.reauthorize",
127
+ "integration.disconnect"
128
+ ],
129
+ "when": { "path": "/actor/type", "equals": "admin" },
130
+ "requiredPaths": ["/authentication"]
131
+ },
132
+ {
133
+ "id": "INTEGRATION-KEY-001",
134
+ "description": "An API key lifecycle event names the credential it acted on through a non-secret reference.",
135
+ "rationale": "A key rotation that does not say which key rotated cannot be tied to the calls made before and after it, so neither the exposure window nor the callers that broke can be established. The reference is a handle the producer can resolve; it is never the key. The profile requires the reference precisely so that producers have somewhere non-sensitive to put the answer. Nothing enforces that discipline: the privacy linter catches published credential formats and high-entropy values, but a producer who stores a real key here may well pass it, so this is a requirement on the producer rather than a check.",
136
+ "severity": "error",
137
+ "events": ["api-key.create", "api-key.rotate", "api-key.revoke", "api-key.delete"],
138
+ "requiredMetadata": [{ "path": "/integration/credentialReference", "type": "string" }]
139
+ },
140
+ {
141
+ "id": "INTEGRATION-KEY-002",
142
+ "description": "Issuing or rotating an API key should record what the key may do and when it stops working.",
143
+ "rationale": "Scope and expiry are what turn a credential into a reviewable one: an unbounded, never-expiring key is the finding, and the only moment the answer is known is issuance. Both are recommended rather than required because plenty of legitimate deployments issue keys with no expiry by design, and because scope vocabularies differ enough between products that a required field would be filled with a placeholder.",
144
+ "severity": "warning",
145
+ "events": ["api-key.create", "api-key.rotate"],
146
+ "recommendedPaths": [
147
+ "/metadata/integration/scope",
148
+ "/metadata/integration/expiresAt",
149
+ "/resource/ownerId"
150
+ ]
151
+ },
152
+ {
153
+ "id": "INTEGRATION-REVOKE-001",
154
+ "description": "Revoking, deleting, disabling or disconnecting an API credential, webhook or integration is justified.",
155
+ "rationale": "Withdrawal breaks something that was working for somebody, and the three explanations — planned decommission, incident containment, and mistake — produce identical events unless the reason is recorded. This is also the class of operation most often performed under pressure, when the person who could explain it is busy, so the justification has to be captured at the time or not at all.",
156
+ "severity": "error",
157
+ "events": [
158
+ "api-key.revoke",
159
+ "api-key.delete",
160
+ "webhook.disable",
161
+ "webhook.delete",
162
+ "integration.disconnect",
163
+ "integration.disable",
164
+ "integration.sync.cancel"
165
+ ],
166
+ "requiredPaths": ["/reason"]
167
+ },
168
+ {
169
+ "id": "INTEGRATION-HOOK-001",
170
+ "description": "A webhook administration event identifies the subscription and classifies the destination the callback is delivered to.",
171
+ "rationale": "A webhook is an instruction to push data out of the system on an ongoing basis, so the single fact that determines its risk is where the data goes. The profile requires a classification rather than the callback URL on purpose: delivery URLs routinely carry signed access parameters or shared secrets in their path or query string, so recording one turns the audit trail into a credential store. A class such as an internal service, a partner network or the public internet answers the reviewer's question without carrying anything an attacker can use.",
172
+ "severity": "error",
173
+ "events": [
174
+ "webhook.create",
175
+ "webhook.update",
176
+ "webhook.enable",
177
+ "webhook.disable",
178
+ "webhook.delete",
179
+ "webhook.test"
180
+ ],
181
+ "requiredMetadata": [
182
+ { "path": "/integration/webhookId", "type": "string" },
183
+ { "path": "/integration/endpointClass", "type": "string" }
184
+ ]
185
+ },
186
+ {
187
+ "id": "INTEGRATION-CONFIG-001",
188
+ "description": "Reconfiguring a webhook subscription or an integration records the change itself.",
189
+ "rationale": "An update event that says only that something changed is not reviewable: retry behaviour, event selection, field mapping and destination are all reached through the same operation, and they are not equally consequential. The core `/change` object is where the difference belongs. The changed field names are recommended alongside it because they are the cheapest useful form of the answer and they carry no configuration values; before and after states are neither required nor recommended, because integration configuration frequently contains endpoints and headers that must not be copied into an audit event.",
190
+ "severity": "error",
191
+ "events": ["webhook.update"],
192
+ "eventPrefixes": ["integration.configuration."],
193
+ "requiredPaths": ["/change"],
194
+ "recommendedPaths": ["/change/changedFields"]
195
+ },
196
+ {
197
+ "id": "INTEGRATION-CONN-001",
198
+ "description": "An integration lifecycle, configuration or sync event identifies the connection instance and the external party on the other side of it.",
199
+ "rationale": "Organizations run many connections to the same kind of system and several to the same provider, so neither the provider name nor the resource identifier alone locates the integration that changed. Recording both is what allows a reviewer to answer the question that actually gets asked after a third-party compromise — which of our connections to that party were live, and who changed them — without needing the producer's internal topology.",
200
+ "severity": "error",
201
+ "events": [
202
+ "integration.connect",
203
+ "integration.disconnect",
204
+ "integration.enable",
205
+ "integration.disable",
206
+ "integration.reauthorize",
207
+ "integration.sync.start",
208
+ "integration.sync.cancel"
209
+ ],
210
+ "eventPrefixes": ["integration.configuration."],
211
+ "requiredMetadata": [
212
+ { "path": "/integration/connectionId", "type": "string" },
213
+ { "path": "/integration/provider", "type": "string" }
214
+ ]
215
+ },
216
+ {
217
+ "id": "INTEGRATION-FLOW-001",
218
+ "description": "Connecting, reauthorizing, starting a sync or cancelling a sync records the correlation identifier that ties the steps of the workflow together.",
219
+ "rationale": "These four operations are never a single event. A connection is a redirect, a consent, a callback and a token exchange; a sync is a start, a run and an end. Reconstructing what happened means reassembling those steps, and a producer-assigned correlation identifier is the only thing that makes that possible. It is required here and merely recommended elsewhere because elsewhere there is nothing to correlate with. This is deliberately not a trace identifier: correlation is a value the producer stamps on related events, and requiring `traceId` would make conformance depend on distributed tracing being deployed.",
220
+ "severity": "error",
221
+ "events": [
222
+ "integration.connect",
223
+ "integration.reauthorize",
224
+ "integration.sync.start",
225
+ "integration.sync.cancel"
226
+ ],
227
+ "requiredPaths": ["/request/correlationId"]
228
+ },
229
+ {
230
+ "id": "INTEGRATION-FAIL-001",
231
+ "description": "A failed integration operation classifies the failure, not only names it.",
232
+ "rationale": "The core model already requires a failure code. A code alone is a producer-defined string, so a reviewer looking at a burst of failures cannot tell whether an integration is rejecting credentials, being denied permission, timing out or hitting a quota without learning that producer's vocabulary first. The coarse classification is what makes 'is this a credential problem or a network problem?' answerable across products, and that question decides whether a failure burst is an outage or an attack.",
233
+ "severity": "error",
234
+ "events": [
235
+ "api-key.create",
236
+ "api-key.rotate",
237
+ "api-key.revoke",
238
+ "api-key.delete",
239
+ "webhook.create",
240
+ "webhook.update",
241
+ "webhook.enable",
242
+ "webhook.disable",
243
+ "webhook.delete",
244
+ "webhook.test",
245
+ "integration.connect",
246
+ "integration.disconnect",
247
+ "integration.enable",
248
+ "integration.disable",
249
+ "integration.reauthorize",
250
+ "integration.sync.start",
251
+ "integration.sync.cancel"
252
+ ],
253
+ "eventPrefixes": ["integration.configuration."],
254
+ "when": { "path": "/event/outcome", "equals": "failure" },
255
+ "requiredPaths": ["/event/error/type"]
256
+ }
257
+ ]
258
+ }