@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,235 @@
1
+ import { digestsEqual } from "./digest.js";
2
+ import { verifyEventIntegrity, readIntegrity } from "./verify-event.js";
3
+ function asString(value) {
4
+ return typeof value === "string" ? value : undefined;
5
+ }
6
+ function asSequence(value) {
7
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : undefined;
8
+ }
9
+ /** Orders members deterministically: by sequence, then by label for equal sequences. */
10
+ function compareMembers(left, right) {
11
+ const leftSequence = left.sequence ?? Number.MAX_SAFE_INTEGER;
12
+ const rightSequence = right.sequence ?? Number.MAX_SAFE_INTEGER;
13
+ if (leftSequence !== rightSequence) {
14
+ return leftSequence - rightSequence;
15
+ }
16
+ return left.label.localeCompare(right.label, "en");
17
+ }
18
+ function verifyOneChain(chainId, members, validator) {
19
+ const findings = [];
20
+ const notes = [];
21
+ const checks = [];
22
+ // Every event's own digest must hold before its links mean anything.
23
+ let digestsValid = true;
24
+ for (const member of members) {
25
+ const result = verifyEventIntegrity(member.event, member.label, validator, {
26
+ validateSchema: false,
27
+ });
28
+ if (!result.verified) {
29
+ digestsValid = false;
30
+ findings.push(...result.findings);
31
+ }
32
+ }
33
+ if (digestsValid && members.length > 0) {
34
+ checks.push({ message: `all ${members.length} event digests valid` });
35
+ }
36
+ // A chain is only comparable if every member seals itself the same way: a
37
+ // `previousHash` produced by a different algorithm can never equal the
38
+ // predecessor's `hash`.
39
+ const algorithms = new Set(members.map((member) => member.hashAlgorithm ?? "(none)"));
40
+ const canonicalizations = new Set(members.map((member) => member.canonicalization ?? "(none)"));
41
+ if (algorithms.size > 1 || canonicalizations.size > 1) {
42
+ findings.push({
43
+ kind: "algorithm-mismatch",
44
+ message: "events in this chain do not share one hash algorithm and canonicalization",
45
+ detail: [
46
+ `hash algorithms: ${[...algorithms].sort().join(", ")}`,
47
+ `canonicalizations: ${[...canonicalizations].sort().join(", ")}`,
48
+ ],
49
+ });
50
+ }
51
+ // Ordering is by `sequence`; without it there is no deterministic order.
52
+ const withoutSequence = members.filter((member) => member.sequence === undefined);
53
+ for (const member of withoutSequence) {
54
+ findings.push({
55
+ kind: "sequence-missing",
56
+ label: member.label,
57
+ message: "event declares no sequence, so it cannot be ordered within the chain",
58
+ });
59
+ }
60
+ const ordered = [...members].sort(compareMembers);
61
+ const bySequence = new Map();
62
+ for (const member of ordered) {
63
+ if (member.sequence === undefined) {
64
+ continue;
65
+ }
66
+ const bucket = bySequence.get(member.sequence);
67
+ if (bucket === undefined) {
68
+ bySequence.set(member.sequence, [member]);
69
+ }
70
+ else {
71
+ bucket.push(member);
72
+ }
73
+ }
74
+ for (const [sequence, bucket] of [...bySequence.entries()].sort((a, b) => a[0] - b[0])) {
75
+ if (bucket.length > 1) {
76
+ findings.push({
77
+ kind: "duplicate-sequence",
78
+ message: `sequence ${sequence} is declared by ${bucket.length} events`,
79
+ detail: bucket.map((member) => member.label),
80
+ });
81
+ }
82
+ }
83
+ // Links are compared against the predecessor's *declared* hash. Every declared
84
+ // hash has already been checked against a recalculated digest above, so this
85
+ // is as strong as comparing against the recalculation while keeping a
86
+ // modified event and a broken link reported as separate, locatable problems.
87
+ const linkable = ordered.filter((member) => member.sequence !== undefined);
88
+ let linksValid = true;
89
+ for (const [index, member] of linkable.entries()) {
90
+ if (index === 0) {
91
+ if (member.previousHash !== undefined) {
92
+ notes.push({
93
+ message: "chain does not start at a genesis event",
94
+ detail: [
95
+ `first supplied event ${member.label} declares previousHash`,
96
+ "the supplied set is a segment; the events before it were not verified",
97
+ ],
98
+ });
99
+ }
100
+ continue;
101
+ }
102
+ const predecessor = linkable[index - 1];
103
+ if (predecessor === undefined) {
104
+ continue;
105
+ }
106
+ if (member.previousHash === undefined) {
107
+ linksValid = false;
108
+ findings.push({
109
+ kind: "previous-hash-missing",
110
+ label: member.label,
111
+ message: "event declares no previousHash but is not the first event in the chain",
112
+ });
113
+ continue;
114
+ }
115
+ if (predecessor.hash === undefined || !digestsEqual(member.previousHash, predecessor.hash)) {
116
+ linksValid = false;
117
+ findings.push({
118
+ kind: "broken-link",
119
+ label: member.label,
120
+ message: "previousHash does not match the preceding event",
121
+ detail: [
122
+ `preceding event: ${predecessor.label}`,
123
+ `declared previousHash: ${member.previousHash}`,
124
+ `preceding event hash: ${predecessor.hash ?? "(none declared)"}`,
125
+ ],
126
+ });
127
+ }
128
+ }
129
+ if (linksValid && linkable.length > 1) {
130
+ checks.push({ message: `all ${linkable.length - 1} previous-hash links valid` });
131
+ }
132
+ if (linkable.length > 0 && linkable[0]?.previousHash === undefined) {
133
+ checks.push({ message: "chain starts at a genesis event" });
134
+ }
135
+ // A gap is not a failure: the core model permits non-contiguous sequences, and
136
+ // an event removed from the middle would break a link rather than only a gap.
137
+ // Distinct values are used so that a duplicate is not also reported as a gap.
138
+ const sequences = [...new Set(linkable.map((member) => member.sequence))].sort((a, b) => a - b);
139
+ const first = sequences[0];
140
+ const last = sequences[sequences.length - 1];
141
+ if (first !== undefined && last !== undefined && last - first + 1 !== sequences.length) {
142
+ notes.push({
143
+ message: "sequence numbers are not contiguous",
144
+ detail: [
145
+ `observed: ${sequences.join(", ")}`,
146
+ "the core model permits gaps; a removed event would also break a link",
147
+ ],
148
+ });
149
+ }
150
+ return {
151
+ chainId,
152
+ eventCount: members.length,
153
+ ...(first === undefined ? {} : { firstSequence: first }),
154
+ ...(last === undefined ? {} : { lastSequence: last }),
155
+ intact: findings.length === 0,
156
+ checks,
157
+ findings,
158
+ notes,
159
+ };
160
+ }
161
+ /**
162
+ * Verifies every chain present in a set of events.
163
+ *
164
+ * Events are grouped by `integrity.chainId`; a set containing several chains is
165
+ * verified as several independent chains, which is the intended model — a
166
+ * single global chain is never required.
167
+ */
168
+ export function verifyChains(inputs, validator) {
169
+ const unassigned = [];
170
+ const groups = new Map();
171
+ for (const input of inputs) {
172
+ const issues = validator.validateEvent(input.event);
173
+ if (issues.length > 0) {
174
+ const shown = issues.slice(0, 3).map((issue) => `${issue.path} ${issue.message}`);
175
+ unassigned.push({
176
+ kind: "schema-invalid",
177
+ label: input.label,
178
+ message: "event does not conform to the canonical schema",
179
+ detail: issues.length > shown.length
180
+ ? [...shown, `and ${issues.length - shown.length} further schema issues`]
181
+ : shown,
182
+ });
183
+ continue;
184
+ }
185
+ const integrity = readIntegrity(input.event);
186
+ if (integrity === undefined) {
187
+ unassigned.push({
188
+ kind: "integrity-missing",
189
+ label: input.label,
190
+ message: "event carries no integrity object and cannot belong to a chain",
191
+ });
192
+ continue;
193
+ }
194
+ const chainId = asString(integrity.chainId);
195
+ if (chainId === undefined) {
196
+ unassigned.push({
197
+ kind: "chain-id-missing",
198
+ label: input.label,
199
+ message: "event declares no integrity.chainId, so it cannot be assigned to a chain",
200
+ });
201
+ continue;
202
+ }
203
+ const sequence = asSequence(input.event["sequence"]);
204
+ const hash = asString(integrity.hash);
205
+ const previousHash = asString(integrity.previousHash);
206
+ const hashAlgorithm = asString(integrity.hashAlgorithm);
207
+ const canonicalization = asString(integrity.canonicalization);
208
+ const member = {
209
+ label: input.label,
210
+ event: input.event,
211
+ ...(sequence === undefined ? {} : { sequence }),
212
+ ...(hash === undefined ? {} : { hash }),
213
+ ...(previousHash === undefined ? {} : { previousHash }),
214
+ ...(hashAlgorithm === undefined ? {} : { hashAlgorithm }),
215
+ ...(canonicalization === undefined ? {} : { canonicalization }),
216
+ };
217
+ const group = groups.get(chainId);
218
+ if (group === undefined) {
219
+ groups.set(chainId, [member]);
220
+ }
221
+ else {
222
+ group.push(member);
223
+ }
224
+ }
225
+ const chains = [...groups.entries()]
226
+ .sort((left, right) => left[0].localeCompare(right[0], "en"))
227
+ .map(([chainId, members]) => verifyOneChain(chainId, members, validator));
228
+ return {
229
+ chains,
230
+ unassigned,
231
+ eventCount: inputs.length,
232
+ intact: unassigned.length === 0 && chains.every((chain) => chain.intact),
233
+ };
234
+ }
235
+ //# sourceMappingURL=verify-chain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-chain.js","sourceRoot":"","sources":["../../../../conformance/src/integrity/verify-chain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Verification of a single event's own digest.
3
+ *
4
+ * This proves that the event has not been altered since its digest was
5
+ * calculated. It proves nothing about whether the event was ever stored, is
6
+ * still stored, or belongs to a complete chain.
7
+ */
8
+ import type { EventValidator } from "../validate-core.js";
9
+ import { type EventVerificationResult } from "./types.js";
10
+ export interface VerifyEventOptions {
11
+ /** Validate against the canonical schema first. Defaults to true. */
12
+ readonly validateSchema?: boolean;
13
+ }
14
+ /** The integrity object of an event, once it is known to be an object. */
15
+ interface IntegrityObject {
16
+ readonly canonicalization?: unknown;
17
+ readonly hashAlgorithm?: unknown;
18
+ readonly hash?: unknown;
19
+ readonly previousHash?: unknown;
20
+ readonly chainId?: unknown;
21
+ }
22
+ /** Reads the integrity object of an event, or `undefined` when there is none. */
23
+ export declare function readIntegrity(event: unknown): IntegrityObject | undefined;
24
+ /**
25
+ * Verifies that an event's declared `integrity.hash` matches a digest
26
+ * recalculated from the event itself.
27
+ */
28
+ export declare function verifyEventIntegrity(event: unknown, label: string, validator: EventValidator, options?: VerifyEventOptions): EventVerificationResult;
29
+ export {};
@@ -0,0 +1,140 @@
1
+ import { CanonicalizationError, isSupportedCanonicalization } from "./canonicalize.js";
2
+ import { calculateDigest, digestByteLength, digestsEqual, isHexDigest, isSupportedHashAlgorithm, } from "./digest.js";
3
+ import { SUPPORTED_CANONICALIZATIONS, SUPPORTED_HASH_ALGORITHMS, } from "./types.js";
4
+ function failure(label, finding, checks = []) {
5
+ return { label, verified: false, checks, findings: [{ ...finding, label }] };
6
+ }
7
+ /** Reads the integrity object of an event, or `undefined` when there is none. */
8
+ export function readIntegrity(event) {
9
+ if (event === null || typeof event !== "object" || Array.isArray(event)) {
10
+ return undefined;
11
+ }
12
+ const integrity = event["integrity"];
13
+ if (integrity === null || typeof integrity !== "object" || Array.isArray(integrity)) {
14
+ return undefined;
15
+ }
16
+ return integrity;
17
+ }
18
+ /**
19
+ * Verifies that an event's declared `integrity.hash` matches a digest
20
+ * recalculated from the event itself.
21
+ */
22
+ export function verifyEventIntegrity(event, label, validator, options = {}) {
23
+ const checks = [];
24
+ if (options.validateSchema !== false) {
25
+ const issues = validator.validateEvent(event);
26
+ if (issues.length > 0) {
27
+ const shown = issues.slice(0, 3).map((issue) => `${issue.path} ${issue.message}`);
28
+ const detail = issues.length > shown.length
29
+ ? [...shown, `and ${issues.length - shown.length} further schema issues`]
30
+ : shown;
31
+ return failure(label, {
32
+ kind: "schema-invalid",
33
+ message: "event does not conform to the canonical schema",
34
+ detail,
35
+ });
36
+ }
37
+ checks.push({ message: "schema valid" });
38
+ }
39
+ const integrity = readIntegrity(event);
40
+ if (integrity === undefined) {
41
+ return failure(label, {
42
+ kind: "integrity-missing",
43
+ message: "event carries no integrity object, so there is nothing to verify",
44
+ detail: ["integrity is optional in the core model; this command requires it"],
45
+ }, checks);
46
+ }
47
+ const { canonicalization, hashAlgorithm, hash } = integrity;
48
+ if (typeof hash !== "string") {
49
+ return failure(label, {
50
+ kind: "hash-missing",
51
+ message: "integrity object declares no hash",
52
+ }, checks);
53
+ }
54
+ if (typeof canonicalization !== "string") {
55
+ return failure(label, {
56
+ kind: "canonicalization-missing",
57
+ message: "integrity object declares a hash but no canonicalization",
58
+ }, checks);
59
+ }
60
+ if (!isSupportedCanonicalization(canonicalization)) {
61
+ return failure(label, {
62
+ kind: "unsupported-canonicalization",
63
+ message: `canonicalization "${canonicalization}" is not implemented by this verifier`,
64
+ detail: [`implemented: ${SUPPORTED_CANONICALIZATIONS.join(", ")}`],
65
+ }, checks);
66
+ }
67
+ checks.push({ message: `canonicalization: ${canonicalization}` });
68
+ if (typeof hashAlgorithm !== "string") {
69
+ return failure(label, {
70
+ kind: "hash-algorithm-missing",
71
+ message: "integrity object declares a hash but no hash algorithm",
72
+ }, checks);
73
+ }
74
+ if (!isSupportedHashAlgorithm(hashAlgorithm)) {
75
+ return failure(label, {
76
+ kind: "unsupported-algorithm",
77
+ message: `hash algorithm "${hashAlgorithm}" is not implemented by this verifier`,
78
+ detail: [
79
+ `implemented: ${SUPPORTED_HASH_ALGORITHMS.join(", ")}`,
80
+ "the schema keeps this vocabulary open; acceptance by the schema is not support",
81
+ ],
82
+ }, checks);
83
+ }
84
+ checks.push({ message: `hash algorithm: ${hashAlgorithm}` });
85
+ if (!isHexDigest(hash)) {
86
+ return failure(label, {
87
+ kind: "malformed-hash",
88
+ message: "declared hash is not lowercase hexadecimal",
89
+ detail: ["digests are encoded as lowercase hexadecimal; see specification/integrity.md §5"],
90
+ }, checks);
91
+ }
92
+ const expectedLength = digestByteLength(hashAlgorithm) * 2;
93
+ if (hash.length !== expectedLength) {
94
+ return failure(label, {
95
+ kind: "digest-length-mismatch",
96
+ message: `declared hash is ${hash.length} characters, but ${hashAlgorithm} produces ${expectedLength}`,
97
+ }, checks);
98
+ }
99
+ let calculated;
100
+ try {
101
+ calculated = calculateDigest(event, hashAlgorithm);
102
+ }
103
+ catch (cause) {
104
+ const message = cause instanceof CanonicalizationError
105
+ ? cause.message
106
+ : `digest could not be calculated: ${cause.message}`;
107
+ return failure(label, { kind: "canonicalization-failed", message }, checks);
108
+ }
109
+ if (!digestsEqual(hash, calculated)) {
110
+ return {
111
+ label,
112
+ verified: false,
113
+ checks,
114
+ findings: [
115
+ {
116
+ kind: "hash-mismatch",
117
+ label,
118
+ message: "integrity hash mismatch",
119
+ detail: [`declared: ${hash}`, `calculated: ${calculated}`],
120
+ },
121
+ ],
122
+ canonicalization,
123
+ hashAlgorithm,
124
+ declaredHash: hash,
125
+ calculatedHash: calculated,
126
+ };
127
+ }
128
+ checks.push({ message: "integrity hash valid" });
129
+ return {
130
+ label,
131
+ verified: true,
132
+ checks,
133
+ findings: [],
134
+ canonicalization,
135
+ hashAlgorithm,
136
+ declaredHash: hash,
137
+ calculatedHash: calculated,
138
+ };
139
+ }
140
+ //# sourceMappingURL=verify-event.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-event.js","sourceRoot":"","sources":["../../../../conformance/src/integrity/verify-event.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /** Shortest string the heuristic will consider. */
2
+ export declare const MIN_ENTROPY_LENGTH = 24;
3
+ /** Longest string the heuristic will consider; longer values are a size problem. */
4
+ export declare const MAX_ENTROPY_LENGTH = 4096;
5
+ /** Minimum Shannon entropy, in bits per character. */
6
+ export declare const ENTROPY_THRESHOLD = 4;
7
+ /** Minimum number of distinct character classes. */
8
+ export declare const MIN_CHARACTER_CLASSES = 3;
9
+ /** Shannon entropy of a string, in bits per character. */
10
+ export declare function shannonEntropy(value: string): number;
11
+ /** Number of distinct character classes present: lower, upper, digit, symbol. */
12
+ export declare function characterClassCount(value: string): number;
13
+ /** True when a value passes every gate of the entropy heuristic. */
14
+ export declare function isHighEntropyTokenCandidate(value: string): boolean;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The entropy heuristic.
3
+ *
4
+ * This is the only rule in the linter that guesses. It exists because a secret
5
+ * can be stored under a harmless name in a format nobody publishes, and it is
6
+ * deliberately conservative: every gate below is there to keep it quiet, and it
7
+ * reports at low confidence unless the surrounding property name is itself
8
+ * suspicious.
9
+ *
10
+ * A value must pass **all** of these to be reported:
11
+ *
12
+ * 1. It is a string of at least 24 and at most 4096 characters.
13
+ * 2. It contains no whitespace, so ordinary prose is excluded.
14
+ * 3. Every character is drawn from the token alphabet `A-Za-z0-9+/=_.-`.
15
+ * 4. It uses at least 3 of the 4 character classes: lower case, upper case,
16
+ * digits, symbols. This excludes single-case words and hexadecimal runs.
17
+ * 5. Its Shannon entropy is at least 4.0 bits per character.
18
+ * 6. It is not a recognised safe format (see safe-formats.ts).
19
+ *
20
+ * The thresholds are fixed in v0.1 and are documented in
21
+ * specification/privacy.md §6.
22
+ */
23
+ import { isKnownSafeFormat } from "./safe-formats.js";
24
+ /** Shortest string the heuristic will consider. */
25
+ export const MIN_ENTROPY_LENGTH = 24;
26
+ /** Longest string the heuristic will consider; longer values are a size problem. */
27
+ export const MAX_ENTROPY_LENGTH = 4096;
28
+ /** Minimum Shannon entropy, in bits per character. */
29
+ export const ENTROPY_THRESHOLD = 4.0;
30
+ /** Minimum number of distinct character classes. */
31
+ export const MIN_CHARACTER_CLASSES = 3;
32
+ /** Characters a token may be built from. */
33
+ const TOKEN_ALPHABET = /^[A-Za-z0-9+/=_.-]+$/;
34
+ /** Shannon entropy of a string, in bits per character. */
35
+ export function shannonEntropy(value) {
36
+ if (value.length === 0) {
37
+ return 0;
38
+ }
39
+ const counts = new Map();
40
+ for (const character of value) {
41
+ counts.set(character, (counts.get(character) ?? 0) + 1);
42
+ }
43
+ let entropy = 0;
44
+ for (const count of counts.values()) {
45
+ const probability = count / value.length;
46
+ entropy -= probability * Math.log2(probability);
47
+ }
48
+ return entropy;
49
+ }
50
+ /** Number of distinct character classes present: lower, upper, digit, symbol. */
51
+ export function characterClassCount(value) {
52
+ let classes = 0;
53
+ if (/[a-z]/.test(value)) {
54
+ classes += 1;
55
+ }
56
+ if (/[A-Z]/.test(value)) {
57
+ classes += 1;
58
+ }
59
+ if (/[0-9]/.test(value)) {
60
+ classes += 1;
61
+ }
62
+ if (/[^A-Za-z0-9]/.test(value)) {
63
+ classes += 1;
64
+ }
65
+ return classes;
66
+ }
67
+ /** True when a value passes every gate of the entropy heuristic. */
68
+ export function isHighEntropyTokenCandidate(value) {
69
+ if (value.length < MIN_ENTROPY_LENGTH || value.length > MAX_ENTROPY_LENGTH) {
70
+ return false;
71
+ }
72
+ if (/\s/.test(value)) {
73
+ return false;
74
+ }
75
+ if (!TOKEN_ALPHABET.test(value)) {
76
+ return false;
77
+ }
78
+ if (characterClassCount(value) < MIN_CHARACTER_CLASSES) {
79
+ return false;
80
+ }
81
+ if (isKnownSafeFormat(value)) {
82
+ return false;
83
+ }
84
+ return shannonEntropy(value) >= ENTROPY_THRESHOLD;
85
+ }
86
+ //# sourceMappingURL=entropy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entropy.js","sourceRoot":"","sources":["../../../../conformance/src/privacy/entropy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,mDAAmD;AACnD,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAErC,oFAAoF;AACpF,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEvC,sDAAsD;AACtD,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAErC,oDAAoD;AACpD,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEvC,4CAA4C;AAC5C,MAAM,cAAc,GAAG,sBAAsB,CAAC;AAE9C,0DAA0D;AAC1D,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,SAAS,IAAI,KAAK,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;QACpC,MAAM,WAAW,GAAG,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC;QACzC,OAAO,IAAI,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAClD,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC,CAAC;IACf,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC,CAAC;IACf,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC,CAAC;IACf,CAAC;IACD,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC,CAAC;IACf,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,2BAA2B,CAAC,KAAa;IACvD,IAAI,KAAK,CAAC,MAAM,GAAG,kBAAkB,IAAI,KAAK,CAAC,MAAM,GAAG,kBAAkB,EAAE,CAAC;QAC3E,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,mBAAmB,CAAC,KAAK,CAAC,GAAG,qBAAqB,EAAE,CAAC;QACvD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,cAAc,CAAC,KAAK,CAAC,IAAI,iBAAiB,CAAC;AACpD,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Property-name registries.
3
+ *
4
+ * Matching is **exact after normalization**, never substring. Substring
5
+ * matching is what makes name-based linters unusable: `passwordPolicy`,
6
+ * `secretRotationEnabled`, `tokenCount`, `authorizationDecision` and
7
+ * `cookieConsent` are all legitimate audit fields that a substring rule would
8
+ * flag, and a rule that cries wolf is a rule that gets switched off.
9
+ *
10
+ * Normalization removes only case and the separators that the same concept is
11
+ * spelled with in different systems: `clientSecret`, `client_secret`,
12
+ * `client-secret` and `Client.Secret` are one name.
13
+ */
14
+ /** Lower-cases a property name and removes `-`, `_`, `.` and spaces. */
15
+ export declare function normalizeFieldName(name: string): string;
16
+ /**
17
+ * Property names whose value is a credential by definition. A field with one of
18
+ * these names has no non-sensitive interpretation.
19
+ */
20
+ export declare const PROHIBITED_CREDENTIAL_FIELD_NAMES: ReadonlySet<string>;
21
+ /**
22
+ * Property names that carry whole request, response or message bodies. These
23
+ * are prohibited by specification/privacy.md §2 from being captured
24
+ * automatically, and a populated field with one of these names is evidence that
25
+ * they were.
26
+ */
27
+ export declare const RAW_PAYLOAD_FIELD_NAMES: ReadonlySet<string>;
28
+ /**
29
+ * Names that are not credentials by themselves but that make a high-entropy
30
+ * value more suspicious. Used only to raise the confidence of the entropy rule
31
+ * from `low` to `medium`; never to raise a finding on its own.
32
+ */
33
+ export declare const SUSPICIOUS_FIELD_NAME_HINTS: ReadonlySet<string>;
34
+ export declare function isProhibitedCredentialFieldName(name: string): boolean;
35
+ export declare function isRawPayloadFieldName(name: string): boolean;
36
+ export declare function isSuspiciousFieldName(name: string): boolean;