trustdex 0.10.0 → 0.11.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.
- package/README.ko.md +30 -11
- package/README.md +30 -12
- package/bin/trustdex.mjs +82 -1
- package/docs/ARCHITECTURE.ko.md +7 -3
- package/docs/ARCHITECTURE.md +7 -3
- package/docs/CLAUDE.ko.md +4 -2
- package/docs/CLAUDE.md +5 -2
- package/docs/CODEX.ko.md +7 -5
- package/docs/CODEX.md +7 -5
- package/docs/DEFENSIVE_USE_CASE.ko.md +6 -4
- package/docs/DEFENSIVE_USE_CASE.md +7 -5
- package/docs/EVALUATION.ko.md +11 -4
- package/docs/EVALUATION.md +11 -4
- package/docs/ROADMAP.ko.md +5 -1
- package/docs/ROADMAP.md +5 -1
- package/docs/RUNTIME_EVIDENCE.ko.md +89 -0
- package/docs/RUNTIME_EVIDENCE.md +96 -0
- package/docs/THREAT_MODEL.ko.md +6 -1
- package/docs/THREAT_MODEL.md +6 -1
- package/examples/adversarial/bindings/expected.json +1 -1
- package/package.json +3 -3
- package/schemas/effective-runtime-tools-diff-v2.schema.json +51 -0
- package/schemas/effective-runtime-tools-v2.schema.json +98 -0
- package/schemas/runtime-tool-inventory-attestation-v1.schema.json +27 -0
- package/src/runtime-attestation.mjs +202 -0
- package/src/runtime-evidence.mjs +88 -14
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { canonicalJson, sha256Json } from './canonical.mjs';
|
|
4
|
+
import {
|
|
5
|
+
normalizeRuntimeToolInventory,
|
|
6
|
+
runtimeToolInventoryDigest
|
|
7
|
+
} from './runtime-evidence.mjs';
|
|
8
|
+
|
|
9
|
+
const KIND = 'agent-runtime-tool-inventory-attestation';
|
|
10
|
+
const ALGORITHM = 'ed25519';
|
|
11
|
+
const MAX_TTL_SECONDS = 86_400;
|
|
12
|
+
const MAX_FUTURE_SKEW_SECONDS = 300;
|
|
13
|
+
const FIELDS = new Set([
|
|
14
|
+
'schemaVersion', 'kind', 'algorithm', 'inventoryDigest', 'runtime', 'capturedAt',
|
|
15
|
+
'captureMethod', 'producerKeyId', 'createdAt', 'expiresAt', 'signature'
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
function requireObject(value, message) {
|
|
19
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(message);
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function requireExactKeys(value, expected, message) {
|
|
24
|
+
const keys = Object.keys(requireObject(value, message));
|
|
25
|
+
if (keys.length !== expected.size || keys.some((key) => !expected.has(key))) throw new Error(message);
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function requireDigest(value, field) {
|
|
30
|
+
if (typeof value !== 'string' || !/^[a-f0-9]{64}$/.test(value)) {
|
|
31
|
+
throw new Error(`Invalid runtime inventory attestation digest: ${field}`);
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function requireTimestamp(value, field) {
|
|
37
|
+
if (typeof value !== 'string' || Number.isNaN(Date.parse(value)) ||
|
|
38
|
+
new Date(value).toISOString() !== value) {
|
|
39
|
+
throw new Error(`Invalid runtime inventory attestation timestamp: ${field}`);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function publicKeyId(publicKey) {
|
|
45
|
+
const key = publicKey?.type === 'public' ? publicKey : crypto.createPublicKey(publicKey);
|
|
46
|
+
if (key.asymmetricKeyType !== 'ed25519') {
|
|
47
|
+
throw new Error('Runtime inventory attestations require an Ed25519 key.');
|
|
48
|
+
}
|
|
49
|
+
const der = key.export({ type: 'spki', format: 'der' });
|
|
50
|
+
return crypto.createHash('sha256').update(der).digest('hex');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function payload(record) {
|
|
54
|
+
return {
|
|
55
|
+
schemaVersion: record.schemaVersion,
|
|
56
|
+
kind: record.kind,
|
|
57
|
+
algorithm: record.algorithm,
|
|
58
|
+
inventoryDigest: record.inventoryDigest,
|
|
59
|
+
runtime: record.runtime,
|
|
60
|
+
capturedAt: record.capturedAt,
|
|
61
|
+
captureMethod: record.captureMethod,
|
|
62
|
+
producerKeyId: record.producerKeyId,
|
|
63
|
+
createdAt: record.createdAt,
|
|
64
|
+
expiresAt: record.expiresAt
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function validateRuntimeInventoryAttestation(record) {
|
|
69
|
+
requireExactKeys(record, FIELDS, 'Invalid runtime inventory attestation.');
|
|
70
|
+
if (record.schemaVersion !== 1 || record.kind !== KIND || record.algorithm !== ALGORITHM) {
|
|
71
|
+
throw new Error('Unsupported runtime inventory attestation schema.');
|
|
72
|
+
}
|
|
73
|
+
requireDigest(record.inventoryDigest, 'inventoryDigest');
|
|
74
|
+
requireDigest(record.producerKeyId, 'producerKeyId');
|
|
75
|
+
if (!['claude', 'codex'].includes(record.runtime)) throw new Error('Invalid attested runtime.');
|
|
76
|
+
if (!['runtime-export', 'runtime-adapter'].includes(record.captureMethod)) {
|
|
77
|
+
throw new Error('Invalid attested capture method.');
|
|
78
|
+
}
|
|
79
|
+
requireTimestamp(record.capturedAt, 'capturedAt');
|
|
80
|
+
requireTimestamp(record.createdAt, 'createdAt');
|
|
81
|
+
requireTimestamp(record.expiresAt, 'expiresAt');
|
|
82
|
+
if (Date.parse(record.expiresAt) <= Date.parse(record.createdAt)) {
|
|
83
|
+
throw new Error('Runtime inventory attestation expiry must follow creation time.');
|
|
84
|
+
}
|
|
85
|
+
if (Date.parse(record.expiresAt) - Date.parse(record.createdAt) > MAX_TTL_SECONDS * 1000) {
|
|
86
|
+
throw new Error('Runtime inventory attestation lifetime exceeds 24 hours.');
|
|
87
|
+
}
|
|
88
|
+
if (Date.parse(record.capturedAt) > Date.parse(record.createdAt) + MAX_FUTURE_SKEW_SECONDS * 1000) {
|
|
89
|
+
throw new Error('Runtime inventory capture cannot postdate attestation creation by more than five minutes.');
|
|
90
|
+
}
|
|
91
|
+
if (typeof record.signature !== 'string' ||
|
|
92
|
+
!/^[A-Za-z0-9+/]{86}==$/.test(record.signature) ||
|
|
93
|
+
Buffer.from(record.signature, 'base64').toString('base64') !== record.signature) {
|
|
94
|
+
throw new Error('Invalid runtime inventory attestation signature.');
|
|
95
|
+
}
|
|
96
|
+
return record;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function createRuntimeInventoryAttestation(inventoryInput, privateKeyPem, options = {}) {
|
|
100
|
+
const inventory = normalizeRuntimeToolInventory(inventoryInput);
|
|
101
|
+
const ttlSeconds = options.ttlSeconds === undefined ? 300 : options.ttlSeconds;
|
|
102
|
+
if (!Number.isSafeInteger(ttlSeconds) || ttlSeconds < 1 || ttlSeconds > MAX_TTL_SECONDS) {
|
|
103
|
+
throw new Error('Runtime inventory attestation TTL must be an integer from 1 to 86400 seconds.');
|
|
104
|
+
}
|
|
105
|
+
const createdAt = options.createdAt || new Date().toISOString();
|
|
106
|
+
requireTimestamp(createdAt, 'createdAt');
|
|
107
|
+
if (Date.parse(inventory.capturedAt) > Date.parse(createdAt) + MAX_FUTURE_SKEW_SECONDS * 1000) {
|
|
108
|
+
throw new Error('Runtime inventory capture cannot postdate attestation creation by more than five minutes.');
|
|
109
|
+
}
|
|
110
|
+
const expiresAt = new Date(Date.parse(createdAt) + ttlSeconds * 1000).toISOString();
|
|
111
|
+
const publicKey = crypto.createPublicKey(privateKeyPem);
|
|
112
|
+
const record = {
|
|
113
|
+
schemaVersion: 1,
|
|
114
|
+
kind: KIND,
|
|
115
|
+
algorithm: ALGORITHM,
|
|
116
|
+
inventoryDigest: runtimeToolInventoryDigest(inventory),
|
|
117
|
+
runtime: inventory.runtime,
|
|
118
|
+
capturedAt: inventory.capturedAt,
|
|
119
|
+
captureMethod: inventory.captureMethod,
|
|
120
|
+
producerKeyId: publicKeyId(publicKey),
|
|
121
|
+
createdAt,
|
|
122
|
+
expiresAt
|
|
123
|
+
};
|
|
124
|
+
const signature = crypto.sign(
|
|
125
|
+
null,
|
|
126
|
+
Buffer.from(canonicalJson(payload(record))),
|
|
127
|
+
privateKeyPem
|
|
128
|
+
);
|
|
129
|
+
return { ...record, signature: signature.toString('base64') };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function invalid(reason) {
|
|
133
|
+
return { valid: false, reason, producerKeyId: null, inventoryDigest: null, attestationDigest: null };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function verifyRuntimeInventoryAttestation(inventoryInput, recordInput, publicKeyPem, options = {}) {
|
|
137
|
+
let inventory;
|
|
138
|
+
let record;
|
|
139
|
+
try {
|
|
140
|
+
inventory = normalizeRuntimeToolInventory(inventoryInput);
|
|
141
|
+
record = validateRuntimeInventoryAttestation(recordInput);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return invalid(error.message);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let keyId;
|
|
147
|
+
try { keyId = publicKeyId(publicKeyPem); } catch {
|
|
148
|
+
return invalid('Invalid runtime inventory attestation public key.');
|
|
149
|
+
}
|
|
150
|
+
if (record.producerKeyId !== keyId) return invalid('Runtime inventory producer key does not match.');
|
|
151
|
+
const inventoryDigest = runtimeToolInventoryDigest(inventory);
|
|
152
|
+
if (record.inventoryDigest !== inventoryDigest ||
|
|
153
|
+
record.runtime !== inventory.runtime ||
|
|
154
|
+
record.capturedAt !== inventory.capturedAt ||
|
|
155
|
+
record.captureMethod !== inventory.captureMethod) {
|
|
156
|
+
return invalid('Runtime inventory does not match its attestation.');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const nowMs = options.now === undefined ? Date.now() : Date.parse(options.now);
|
|
160
|
+
if (!Number.isFinite(nowMs)) return invalid('Invalid runtime inventory verification time.');
|
|
161
|
+
if (nowMs >= Date.parse(record.expiresAt)) return invalid('Runtime inventory attestation expired.');
|
|
162
|
+
const futureSkewSeconds = options.futureSkewSeconds === undefined
|
|
163
|
+
? MAX_FUTURE_SKEW_SECONDS
|
|
164
|
+
: options.futureSkewSeconds;
|
|
165
|
+
if (!Number.isSafeInteger(futureSkewSeconds) || futureSkewSeconds < 0) {
|
|
166
|
+
return invalid('Invalid runtime inventory future-skew limit.');
|
|
167
|
+
}
|
|
168
|
+
if (Date.parse(record.createdAt) > nowMs + futureSkewSeconds * 1000) {
|
|
169
|
+
return invalid('Runtime inventory attestation creation time is too far in the future.');
|
|
170
|
+
}
|
|
171
|
+
if (Date.parse(inventory.capturedAt) > nowMs + futureSkewSeconds * 1000) {
|
|
172
|
+
return invalid('Runtime inventory capture time is too far in the future.');
|
|
173
|
+
}
|
|
174
|
+
if (options.maxAgeSeconds !== undefined) {
|
|
175
|
+
if (!Number.isSafeInteger(options.maxAgeSeconds) || options.maxAgeSeconds < 0) {
|
|
176
|
+
return invalid('Invalid runtime inventory maximum age.');
|
|
177
|
+
}
|
|
178
|
+
if (nowMs - Date.parse(inventory.capturedAt) > options.maxAgeSeconds * 1000) {
|
|
179
|
+
return invalid('Runtime inventory capture is too old.');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let valid = false;
|
|
184
|
+
try {
|
|
185
|
+
valid = crypto.verify(
|
|
186
|
+
null,
|
|
187
|
+
Buffer.from(canonicalJson(payload(record))),
|
|
188
|
+
publicKeyPem,
|
|
189
|
+
Buffer.from(record.signature, 'base64')
|
|
190
|
+
);
|
|
191
|
+
} catch {
|
|
192
|
+
return invalid('Runtime inventory attestation signature verification failed.');
|
|
193
|
+
}
|
|
194
|
+
if (!valid) return invalid('Runtime inventory attestation signature verification failed.');
|
|
195
|
+
return {
|
|
196
|
+
valid: true,
|
|
197
|
+
reason: null,
|
|
198
|
+
producerKeyId: keyId,
|
|
199
|
+
inventoryDigest,
|
|
200
|
+
attestationDigest: sha256Json(record)
|
|
201
|
+
};
|
|
202
|
+
}
|
package/src/runtime-evidence.mjs
CHANGED
|
@@ -39,7 +39,7 @@ function requireTimestamp(value, field) {
|
|
|
39
39
|
return value;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
function
|
|
42
|
+
export function normalizeRuntimeToolInventory(input) {
|
|
43
43
|
const inventory = requireExactKeys(input, new Set([
|
|
44
44
|
'schemaVersion', 'kind', 'runtime', 'capturedAt', 'captureMethod', 'servers'
|
|
45
45
|
]), 'Invalid runtime tool inventory.');
|
|
@@ -71,6 +71,10 @@ function normalizeInventory(input) {
|
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
export function runtimeToolInventoryDigest(input) {
|
|
75
|
+
return sha256Json(normalizeRuntimeToolInventory(input));
|
|
76
|
+
}
|
|
77
|
+
|
|
74
78
|
function serverStatus(binding, observed) {
|
|
75
79
|
if (!binding) return 'unexpected';
|
|
76
80
|
if (!observed) return 'not-observed';
|
|
@@ -95,15 +99,41 @@ function createSummary(servers) {
|
|
|
95
99
|
};
|
|
96
100
|
}
|
|
97
101
|
|
|
98
|
-
function
|
|
102
|
+
function baseEvidenceStatus(summary) {
|
|
99
103
|
if (summary.blockedCount || summary.unexpectedCount) return 'block';
|
|
100
104
|
if (summary.reviewCount) return 'needs-review';
|
|
101
105
|
if (summary.notObservedCount) return 'restricted';
|
|
102
106
|
return 'matched';
|
|
103
107
|
}
|
|
104
108
|
|
|
109
|
+
function normalizeAuthentication(input, expectedInventoryDigest) {
|
|
110
|
+
if (input === undefined || input === null) {
|
|
111
|
+
return { status: 'unverified', producerKeyId: null, attestationDigest: null };
|
|
112
|
+
}
|
|
113
|
+
requireExactKeys(input, new Set([
|
|
114
|
+
'valid', 'reason', 'producerKeyId', 'inventoryDigest', 'attestationDigest'
|
|
115
|
+
]), 'Invalid runtime inventory authentication.');
|
|
116
|
+
if (input.valid !== true || input.reason !== null) {
|
|
117
|
+
throw new Error('Runtime inventory authentication is not verified.');
|
|
118
|
+
}
|
|
119
|
+
if (requireDigest(input.inventoryDigest, 'inventoryDigest') !== expectedInventoryDigest) {
|
|
120
|
+
throw new Error('Runtime inventory authentication digest mismatch.');
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
status: 'verified',
|
|
124
|
+
producerKeyId: requireDigest(input.producerKeyId, 'producerKeyId'),
|
|
125
|
+
attestationDigest: requireDigest(input.attestationDigest, 'attestationDigest')
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function evidenceStatus(summary, authentication, schemaVersion = 2) {
|
|
130
|
+
if (summary.blockedCount || summary.unexpectedCount) return 'block';
|
|
131
|
+
if (schemaVersion >= 2 && authentication.status !== 'verified') return 'needs-review';
|
|
132
|
+
return baseEvidenceStatus(summary);
|
|
133
|
+
}
|
|
134
|
+
|
|
105
135
|
function evidenceDigest(snapshot) {
|
|
106
|
-
|
|
136
|
+
const projected = {
|
|
107
137
|
schemaVersion: snapshot.schemaVersion,
|
|
108
138
|
kind: snapshot.kind,
|
|
109
139
|
runtime: snapshot.runtime,
|
|
@@ -114,7 +144,13 @@ function evidenceDigest(snapshot) {
|
|
|
114
144
|
status: snapshot.status,
|
|
115
145
|
summary: snapshot.summary,
|
|
116
146
|
servers: snapshot.servers
|
|
117
|
-
}
|
|
147
|
+
};
|
|
148
|
+
if (snapshot.schemaVersion >= 2) {
|
|
149
|
+
projected.inventoryAuthentication = snapshot.inventoryAuthentication;
|
|
150
|
+
projected.producerKeyId = snapshot.producerKeyId;
|
|
151
|
+
projected.attestationDigest = snapshot.attestationDigest;
|
|
152
|
+
}
|
|
153
|
+
return sha256Json(projected);
|
|
118
154
|
}
|
|
119
155
|
|
|
120
156
|
function inventoryFromEvidence(snapshot) {
|
|
@@ -130,9 +166,11 @@ function inventoryFromEvidence(snapshot) {
|
|
|
130
166
|
};
|
|
131
167
|
}
|
|
132
168
|
|
|
133
|
-
export function createEffectiveRuntimeEvidence(bindingInput, inventoryInput) {
|
|
169
|
+
export function createEffectiveRuntimeEvidence(bindingInput, inventoryInput, authenticationInput = null) {
|
|
134
170
|
const bindings = validateToolPermissionBindingSnapshot(bindingInput);
|
|
135
|
-
const inventory =
|
|
171
|
+
const inventory = normalizeRuntimeToolInventory(inventoryInput);
|
|
172
|
+
const inventoryDigest = runtimeToolInventoryDigest(inventory);
|
|
173
|
+
const authentication = normalizeAuthentication(authenticationInput, inventoryDigest);
|
|
136
174
|
if (bindings.runtime !== inventory.runtime) {
|
|
137
175
|
throw new Error('Runtime inventory does not match the binding runtime.');
|
|
138
176
|
}
|
|
@@ -157,15 +195,18 @@ export function createEffectiveRuntimeEvidence(bindingInput, inventoryInput) {
|
|
|
157
195
|
});
|
|
158
196
|
const summary = createSummary(servers);
|
|
159
197
|
const snapshot = {
|
|
160
|
-
schemaVersion:
|
|
198
|
+
schemaVersion: 2,
|
|
161
199
|
kind: KIND,
|
|
162
200
|
createdAt: new Date().toISOString(),
|
|
163
201
|
runtime: inventory.runtime,
|
|
164
202
|
capturedAt: inventory.capturedAt,
|
|
165
203
|
captureMethod: inventory.captureMethod,
|
|
166
204
|
bindingDigest: bindings.digest,
|
|
167
|
-
inventoryDigest
|
|
168
|
-
|
|
205
|
+
inventoryDigest,
|
|
206
|
+
inventoryAuthentication: authentication.status,
|
|
207
|
+
producerKeyId: authentication.producerKeyId,
|
|
208
|
+
attestationDigest: authentication.attestationDigest,
|
|
209
|
+
status: evidenceStatus(summary, authentication),
|
|
169
210
|
summary,
|
|
170
211
|
servers
|
|
171
212
|
};
|
|
@@ -207,11 +248,15 @@ function validateServer(server) {
|
|
|
207
248
|
}
|
|
208
249
|
|
|
209
250
|
function validateEvidence(snapshot) {
|
|
210
|
-
|
|
251
|
+
const baseFields = [
|
|
211
252
|
'schemaVersion', 'kind', 'createdAt', 'runtime', 'capturedAt', 'captureMethod',
|
|
212
253
|
'bindingDigest', 'inventoryDigest', 'status', 'summary', 'servers', 'digest'
|
|
213
|
-
]
|
|
214
|
-
|
|
254
|
+
];
|
|
255
|
+
const expectedFields = new Set(snapshot?.schemaVersion === 2
|
|
256
|
+
? [...baseFields, 'inventoryAuthentication', 'producerKeyId', 'attestationDigest']
|
|
257
|
+
: baseFields);
|
|
258
|
+
requireExactKeys(snapshot, expectedFields, 'Invalid effective runtime evidence snapshot.');
|
|
259
|
+
if (![1, 2].includes(snapshot.schemaVersion) || snapshot.kind !== KIND) {
|
|
215
260
|
throw new Error('Unsupported effective runtime evidence schema.');
|
|
216
261
|
}
|
|
217
262
|
if (!RUNTIMES.has(snapshot.runtime)) throw new Error('Invalid effective runtime evidence runtime.');
|
|
@@ -221,6 +266,23 @@ function validateEvidence(snapshot) {
|
|
|
221
266
|
requireDigest(snapshot.bindingDigest, 'bindingDigest');
|
|
222
267
|
requireDigest(snapshot.inventoryDigest, 'inventoryDigest');
|
|
223
268
|
requireDigest(snapshot.digest, 'digest');
|
|
269
|
+
let authentication = { status: 'unverified', producerKeyId: null, attestationDigest: null };
|
|
270
|
+
if (snapshot.schemaVersion === 2) {
|
|
271
|
+
if (!['unverified', 'verified'].includes(snapshot.inventoryAuthentication)) {
|
|
272
|
+
throw new Error('Invalid runtime inventory authentication state.');
|
|
273
|
+
}
|
|
274
|
+
if (snapshot.inventoryAuthentication === 'verified') {
|
|
275
|
+
authentication = normalizeAuthentication({
|
|
276
|
+
valid: true,
|
|
277
|
+
reason: null,
|
|
278
|
+
producerKeyId: snapshot.producerKeyId,
|
|
279
|
+
inventoryDigest: snapshot.inventoryDigest,
|
|
280
|
+
attestationDigest: snapshot.attestationDigest
|
|
281
|
+
}, snapshot.inventoryDigest);
|
|
282
|
+
} else if (snapshot.producerKeyId !== null || snapshot.attestationDigest !== null) {
|
|
283
|
+
throw new Error('Unverified runtime inventory cannot retain attestation metadata.');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
224
286
|
if (!EVIDENCE_STATUSES.has(snapshot.status)) throw new Error('Invalid effective runtime evidence status.');
|
|
225
287
|
if (!Array.isArray(snapshot.servers)) throw new Error('Effective runtime evidence is missing servers.');
|
|
226
288
|
for (const server of snapshot.servers) validateServer(server);
|
|
@@ -239,7 +301,9 @@ function validateEvidence(snapshot) {
|
|
|
239
301
|
if (snapshot.inventoryDigest !== sha256Json(inventoryFromEvidence(snapshot))) {
|
|
240
302
|
throw new Error('Effective runtime inventory digest mismatch.');
|
|
241
303
|
}
|
|
242
|
-
if (snapshot.status !== evidenceStatus(summary
|
|
304
|
+
if (snapshot.status !== evidenceStatus(summary, authentication, snapshot.schemaVersion)) {
|
|
305
|
+
throw new Error('Effective runtime evidence status mismatch.');
|
|
306
|
+
}
|
|
243
307
|
if (snapshot.digest !== evidenceDigest(snapshot)) throw new Error('Effective runtime evidence digest mismatch.');
|
|
244
308
|
return snapshot;
|
|
245
309
|
}
|
|
@@ -263,6 +327,16 @@ export function diffEffectiveRuntimeEvidence(beforeInput, afterInput) {
|
|
|
263
327
|
if (before.captureMethod !== after.captureMethod) {
|
|
264
328
|
addChange(changes, 'review', 'capture-method-changed', null, 'captureMethod', before.captureMethod, after.captureMethod);
|
|
265
329
|
}
|
|
330
|
+
const beforeAuthentication = before.schemaVersion === 2 ? before.inventoryAuthentication : 'unverified';
|
|
331
|
+
const afterAuthentication = after.schemaVersion === 2 ? after.inventoryAuthentication : 'unverified';
|
|
332
|
+
if (beforeAuthentication !== afterAuthentication) {
|
|
333
|
+
addChange(changes, 'review', 'inventory-authentication-changed', null, 'inventoryAuthentication', beforeAuthentication, afterAuthentication);
|
|
334
|
+
}
|
|
335
|
+
const beforeKeyId = before.schemaVersion === 2 ? before.producerKeyId : null;
|
|
336
|
+
const afterKeyId = after.schemaVersion === 2 ? after.producerKeyId : null;
|
|
337
|
+
if (beforeKeyId !== afterKeyId) {
|
|
338
|
+
addChange(changes, 'review', 'producer-key-changed', null, 'producerKeyId', beforeKeyId, afterKeyId);
|
|
339
|
+
}
|
|
266
340
|
|
|
267
341
|
const left = new Map(before.servers.map((server) => [server.name, server]));
|
|
268
342
|
const right = new Map(after.servers.map((server) => [server.name, server]));
|
|
@@ -306,7 +380,7 @@ export function diffEffectiveRuntimeEvidence(beforeInput, afterInput) {
|
|
|
306
380
|
reviews: changes.filter((change) => change.impact === 'review').length
|
|
307
381
|
};
|
|
308
382
|
return {
|
|
309
|
-
schemaVersion: 1,
|
|
383
|
+
schemaVersion: before.schemaVersion === 2 || after.schemaVersion === 2 ? 2 : 1,
|
|
310
384
|
runtime: before.runtime,
|
|
311
385
|
status: summary.expansions || summary.reviews ? 'needs-review' :
|
|
312
386
|
summary.restrictions ? 'restricted' : 'unchanged',
|