@absolutejs/vulnerabilities-witness 0.1.1 → 0.2.1
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.md +15 -0
- package/dist/index.js +45 -3
- package/dist/server.js +58 -2
- package/dist/service.d.ts +19 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -67,6 +67,15 @@ observations and accepts these deployment secrets:
|
|
|
67
67
|
file
|
|
68
68
|
- `EVIDENCE_WITNESS_KEY_MAX_AGE_MS`, defaulting to 90 days
|
|
69
69
|
- `EVIDENCE_WITNESS_MAINTENANCE_INTERVAL_MS`, defaulting to one hour
|
|
70
|
+
|
|
71
|
+
The authenticated `GET /v1/status` endpoint reports the verified key registry
|
|
72
|
+
and optional backup restoration evidence. Store
|
|
73
|
+
`EVIDENCE_WITNESS_BACKUP_VERIFICATION_JSON` in the encrypted secret adapter
|
|
74
|
+
after a drill has restored both the PostgreSQL artifact and encrypted signing
|
|
75
|
+
state. The record must use the
|
|
76
|
+
`absolutejs.vulnerability-evidence-witness-backup-verification/v1` contract,
|
|
77
|
+
contain SHA-256 digests for both artifacts, and record both restoration times.
|
|
78
|
+
|
|
70
79
|
- `PORT`, defaulting to `3000`
|
|
71
80
|
|
|
72
81
|
The two JSON values bootstrap the encrypted file only when their entries do not
|
|
@@ -77,3 +86,9 @@ Run independent instances under different administrative and infrastructure
|
|
|
77
86
|
boundaries. Clients should pin each genesis witness key through an independent
|
|
78
87
|
channel and require a quorum with
|
|
79
88
|
`verifyEvidenceWitnessQuorum` from `@absolutejs/vulnerabilities`.
|
|
89
|
+
|
|
90
|
+
The repository publishes a deployable container to
|
|
91
|
+
`ghcr.io/absolutejs/vulnerabilities-witness`. See
|
|
92
|
+
[`deploy/README.md`](deploy/README.md) for the independence requirements,
|
|
93
|
+
bootstrap sequence, PAAS quorum configuration, and repeatable Admin drill
|
|
94
|
+
workflow.
|
package/dist/index.js
CHANGED
|
@@ -114,6 +114,33 @@ var verifyEvidenceWitnessKeyRegistry = (input) => {
|
|
|
114
114
|
|
|
115
115
|
// src/service.ts
|
|
116
116
|
var EVIDENCE_WITNESS_REQUEST_CONTRACT = "absolutejs.vulnerability-evidence-witness-request/v1";
|
|
117
|
+
var EVIDENCE_WITNESS_BACKUP_VERIFICATION_CONTRACT = "absolutejs.vulnerability-evidence-witness-backup-verification/v1";
|
|
118
|
+
var EVIDENCE_WITNESS_STATUS_CONTRACT = "absolutejs.vulnerability-evidence-witness-status/v1";
|
|
119
|
+
var timestamp = (value, label) => {
|
|
120
|
+
if (!Number.isFinite(Date.parse(value)))
|
|
121
|
+
throw new Error(`${label} is invalid`);
|
|
122
|
+
return value;
|
|
123
|
+
};
|
|
124
|
+
var digest = (value, label) => {
|
|
125
|
+
if (!/^sha256:[a-f0-9]{64}$/u.test(value))
|
|
126
|
+
throw new Error(`${label} is invalid`);
|
|
127
|
+
return value;
|
|
128
|
+
};
|
|
129
|
+
var parseEvidenceWitnessBackupVerification = (value) => {
|
|
130
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
131
|
+
throw new Error("Evidence witness backup verification is invalid");
|
|
132
|
+
const input = value;
|
|
133
|
+
if (input.contract !== EVIDENCE_WITNESS_BACKUP_VERIFICATION_CONTRACT)
|
|
134
|
+
throw new Error("Evidence witness backup verification is unsupported");
|
|
135
|
+
return {
|
|
136
|
+
contract: input.contract,
|
|
137
|
+
databaseArtifactDigest: digest(input.databaseArtifactDigest ?? "", "Evidence witness database backup digest"),
|
|
138
|
+
databaseRestoredAt: timestamp(input.databaseRestoredAt ?? "", "Evidence witness database restore time"),
|
|
139
|
+
signingStateArtifactDigest: digest(input.signingStateArtifactDigest ?? "", "Evidence witness signing-state backup digest"),
|
|
140
|
+
signingStateRestoredAt: timestamp(input.signingStateRestoredAt ?? "", "Evidence witness signing-state restore time"),
|
|
141
|
+
verifiedAt: timestamp(input.verifiedAt ?? "", "Evidence witness backup verification time")
|
|
142
|
+
};
|
|
143
|
+
};
|
|
117
144
|
var createEvidenceWitnessService = (options) => {
|
|
118
145
|
const origin = new URL(options.origin);
|
|
119
146
|
if (origin.protocol !== "https:")
|
|
@@ -128,6 +155,12 @@ var createEvidenceWitnessService = (options) => {
|
|
|
128
155
|
await refresh();
|
|
129
156
|
return evidenceWitnessKeyRegistryFrom(state);
|
|
130
157
|
};
|
|
158
|
+
const status = async () => ({
|
|
159
|
+
backup: options.loadBackupVerification ? await options.loadBackupVerification().then((value) => value === null ? null : parseEvidenceWitnessBackupVerification(value)) : null,
|
|
160
|
+
checkedAt: new Date().toISOString(),
|
|
161
|
+
contract: EVIDENCE_WITNESS_STATUS_CONTRACT,
|
|
162
|
+
registry: await registry()
|
|
163
|
+
});
|
|
131
164
|
const checkpoint = async (subject, request) => {
|
|
132
165
|
if (request.contract !== EVIDENCE_WITNESS_REQUEST_CONTRACT)
|
|
133
166
|
throw new Error("Evidence witness request contract is unsupported");
|
|
@@ -152,7 +185,7 @@ var createEvidenceWitnessService = (options) => {
|
|
|
152
185
|
});
|
|
153
186
|
const observed = await options.store.observe({
|
|
154
187
|
checkpoint: signed,
|
|
155
|
-
history: request.log.entries.map(({ digest }) =>
|
|
188
|
+
history: request.log.entries.map(({ digest: digest2 }) => digest2),
|
|
156
189
|
subject: normalizedSubject
|
|
157
190
|
});
|
|
158
191
|
return {
|
|
@@ -196,7 +229,7 @@ var createEvidenceWitnessService = (options) => {
|
|
|
196
229
|
});
|
|
197
230
|
return queued;
|
|
198
231
|
};
|
|
199
|
-
return { checkpoint, maintain, registry, rotate };
|
|
232
|
+
return { checkpoint, maintain, registry, rotate, status };
|
|
200
233
|
};
|
|
201
234
|
|
|
202
235
|
// src/store.ts
|
|
@@ -269,6 +302,12 @@ var createEvidenceWitnessHttpHandler = (options) => async (request) => {
|
|
|
269
302
|
return json({ status: "ok" });
|
|
270
303
|
if (request.method === "GET" && url.pathname === "/v1/keys")
|
|
271
304
|
return json(await options.service.registry());
|
|
305
|
+
if (request.method === "GET" && url.pathname === "/v1/status") {
|
|
306
|
+
const subject2 = await options.authenticate(bearer(request));
|
|
307
|
+
if (!subject2)
|
|
308
|
+
return json({ error: "unauthorized" }, 401);
|
|
309
|
+
return json(await options.service.status());
|
|
310
|
+
}
|
|
272
311
|
if (request.method !== "POST" || url.pathname !== "/v1/checkpoints")
|
|
273
312
|
return json({ error: "not_found" }, 404);
|
|
274
313
|
const subject = await options.authenticate(bearer(request));
|
|
@@ -294,6 +333,7 @@ export {
|
|
|
294
333
|
verifyEvidenceWitnessKeyRegistry,
|
|
295
334
|
rotateEvidenceWitnessSigningState,
|
|
296
335
|
parseEvidenceWitnessSigningState,
|
|
336
|
+
parseEvidenceWitnessBackupVerification,
|
|
297
337
|
evidenceWitnessKeyRegistryFrom,
|
|
298
338
|
encodeEvidenceWitnessSigningState,
|
|
299
339
|
createMemoryEvidenceWitnessStore,
|
|
@@ -302,5 +342,7 @@ export {
|
|
|
302
342
|
createEvidenceWitnessHttpHandler,
|
|
303
343
|
EvidenceWitnessRollbackError,
|
|
304
344
|
EvidenceWitnessEquivocationError,
|
|
305
|
-
|
|
345
|
+
EVIDENCE_WITNESS_STATUS_CONTRACT,
|
|
346
|
+
EVIDENCE_WITNESS_REQUEST_CONTRACT,
|
|
347
|
+
EVIDENCE_WITNESS_BACKUP_VERIFICATION_CONTRACT
|
|
306
348
|
};
|
package/dist/server.js
CHANGED
|
@@ -116,6 +116,33 @@ var verifyEvidenceWitnessKeyRegistry = (input) => {
|
|
|
116
116
|
|
|
117
117
|
// src/service.ts
|
|
118
118
|
var EVIDENCE_WITNESS_REQUEST_CONTRACT = "absolutejs.vulnerability-evidence-witness-request/v1";
|
|
119
|
+
var EVIDENCE_WITNESS_BACKUP_VERIFICATION_CONTRACT = "absolutejs.vulnerability-evidence-witness-backup-verification/v1";
|
|
120
|
+
var EVIDENCE_WITNESS_STATUS_CONTRACT = "absolutejs.vulnerability-evidence-witness-status/v1";
|
|
121
|
+
var timestamp = (value, label) => {
|
|
122
|
+
if (!Number.isFinite(Date.parse(value)))
|
|
123
|
+
throw new Error(`${label} is invalid`);
|
|
124
|
+
return value;
|
|
125
|
+
};
|
|
126
|
+
var digest = (value, label) => {
|
|
127
|
+
if (!/^sha256:[a-f0-9]{64}$/u.test(value))
|
|
128
|
+
throw new Error(`${label} is invalid`);
|
|
129
|
+
return value;
|
|
130
|
+
};
|
|
131
|
+
var parseEvidenceWitnessBackupVerification = (value) => {
|
|
132
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
133
|
+
throw new Error("Evidence witness backup verification is invalid");
|
|
134
|
+
const input = value;
|
|
135
|
+
if (input.contract !== EVIDENCE_WITNESS_BACKUP_VERIFICATION_CONTRACT)
|
|
136
|
+
throw new Error("Evidence witness backup verification is unsupported");
|
|
137
|
+
return {
|
|
138
|
+
contract: input.contract,
|
|
139
|
+
databaseArtifactDigest: digest(input.databaseArtifactDigest ?? "", "Evidence witness database backup digest"),
|
|
140
|
+
databaseRestoredAt: timestamp(input.databaseRestoredAt ?? "", "Evidence witness database restore time"),
|
|
141
|
+
signingStateArtifactDigest: digest(input.signingStateArtifactDigest ?? "", "Evidence witness signing-state backup digest"),
|
|
142
|
+
signingStateRestoredAt: timestamp(input.signingStateRestoredAt ?? "", "Evidence witness signing-state restore time"),
|
|
143
|
+
verifiedAt: timestamp(input.verifiedAt ?? "", "Evidence witness backup verification time")
|
|
144
|
+
};
|
|
145
|
+
};
|
|
119
146
|
var createEvidenceWitnessService = (options) => {
|
|
120
147
|
const origin = new URL(options.origin);
|
|
121
148
|
if (origin.protocol !== "https:")
|
|
@@ -130,6 +157,12 @@ var createEvidenceWitnessService = (options) => {
|
|
|
130
157
|
await refresh();
|
|
131
158
|
return evidenceWitnessKeyRegistryFrom(state);
|
|
132
159
|
};
|
|
160
|
+
const status = async () => ({
|
|
161
|
+
backup: options.loadBackupVerification ? await options.loadBackupVerification().then((value) => value === null ? null : parseEvidenceWitnessBackupVerification(value)) : null,
|
|
162
|
+
checkedAt: new Date().toISOString(),
|
|
163
|
+
contract: EVIDENCE_WITNESS_STATUS_CONTRACT,
|
|
164
|
+
registry: await registry()
|
|
165
|
+
});
|
|
133
166
|
const checkpoint = async (subject, request) => {
|
|
134
167
|
if (request.contract !== EVIDENCE_WITNESS_REQUEST_CONTRACT)
|
|
135
168
|
throw new Error("Evidence witness request contract is unsupported");
|
|
@@ -154,7 +187,7 @@ var createEvidenceWitnessService = (options) => {
|
|
|
154
187
|
});
|
|
155
188
|
const observed = await options.store.observe({
|
|
156
189
|
checkpoint: signed,
|
|
157
|
-
history: request.log.entries.map(({ digest }) =>
|
|
190
|
+
history: request.log.entries.map(({ digest: digest2 }) => digest2),
|
|
158
191
|
subject: normalizedSubject
|
|
159
192
|
});
|
|
160
193
|
return {
|
|
@@ -198,7 +231,7 @@ var createEvidenceWitnessService = (options) => {
|
|
|
198
231
|
});
|
|
199
232
|
return queued;
|
|
200
233
|
};
|
|
201
|
-
return { checkpoint, maintain, registry, rotate };
|
|
234
|
+
return { checkpoint, maintain, registry, rotate, status };
|
|
202
235
|
};
|
|
203
236
|
|
|
204
237
|
// src/store.ts
|
|
@@ -271,6 +304,12 @@ var createEvidenceWitnessHttpHandler = (options) => async (request) => {
|
|
|
271
304
|
return json({ status: "ok" });
|
|
272
305
|
if (request.method === "GET" && url.pathname === "/v1/keys")
|
|
273
306
|
return json(await options.service.registry());
|
|
307
|
+
if (request.method === "GET" && url.pathname === "/v1/status") {
|
|
308
|
+
const subject2 = await options.authenticate(bearer(request));
|
|
309
|
+
if (!subject2)
|
|
310
|
+
return json({ error: "unauthorized" }, 401);
|
|
311
|
+
return json(await options.service.status());
|
|
312
|
+
}
|
|
274
313
|
if (request.method !== "POST" || url.pathname !== "/v1/checkpoints")
|
|
275
314
|
return json({ error: "not_found" }, 404);
|
|
276
315
|
const subject = await options.authenticate(bearer(request));
|
|
@@ -384,6 +423,7 @@ var positiveInteger = (name, fallback) => {
|
|
|
384
423
|
};
|
|
385
424
|
var signingStateName = "EVIDENCE_WITNESS_SIGNING_STATE_JSON";
|
|
386
425
|
var tokensName = "EVIDENCE_WITNESS_TOKENS_JSON";
|
|
426
|
+
var backupVerificationName = "EVIDENCE_WITNESS_BACKUP_VERIFICATION_JSON";
|
|
387
427
|
var masterPassphrase = required("EVIDENCE_WITNESS_SECRETS_PASSPHRASE");
|
|
388
428
|
var secrets = encryptedFileAdapter({
|
|
389
429
|
key: { passphrase: masterPassphrase, type: "passphrase" },
|
|
@@ -399,6 +439,18 @@ var loadOrBootstrap = async (name) => {
|
|
|
399
439
|
await secrets.put(name, bootstrap);
|
|
400
440
|
return bootstrap;
|
|
401
441
|
};
|
|
442
|
+
var loadOptionalBootstrap = async (name) => {
|
|
443
|
+
const stored = await secrets.fetch(name);
|
|
444
|
+
if (stored)
|
|
445
|
+
return stored;
|
|
446
|
+
const bootstrap = process.env[name]?.trim();
|
|
447
|
+
if (!bootstrap)
|
|
448
|
+
return null;
|
|
449
|
+
if (!secrets.put)
|
|
450
|
+
throw new Error("Encrypted secret adapter is read-only");
|
|
451
|
+
await secrets.put(name, bootstrap);
|
|
452
|
+
return bootstrap;
|
|
453
|
+
};
|
|
402
454
|
var database = new SQL(required("DATABASE_URL"));
|
|
403
455
|
await ensurePostgresEvidenceWitnessSchema(database);
|
|
404
456
|
var encodedSigningState = await loadOrBootstrap(signingStateName);
|
|
@@ -406,6 +458,10 @@ var signingState = parseEvidenceWitnessSigningState(JSON.parse(encodedSigningSta
|
|
|
406
458
|
var tokens = JSON.parse(await loadOrBootstrap(tokensName));
|
|
407
459
|
var subjectsByToken = new Map(Object.entries(tokens).map(([subject, token]) => [token, subject]));
|
|
408
460
|
var service2 = createEvidenceWitnessService({
|
|
461
|
+
loadBackupVerification: async () => {
|
|
462
|
+
const encoded = await loadOptionalBootstrap(backupVerificationName);
|
|
463
|
+
return encoded ? JSON.parse(encoded) : null;
|
|
464
|
+
},
|
|
409
465
|
loadSigningState: async () => {
|
|
410
466
|
const encoded = await secrets.fetch(signingStateName);
|
|
411
467
|
if (!encoded)
|
package/dist/service.d.ts
CHANGED
|
@@ -10,7 +10,25 @@ export type EvidenceWitnessResponse = {
|
|
|
10
10
|
checkpoint: Awaited<ReturnType<EvidenceWitnessStore["observe"]>>["checkpoint"];
|
|
11
11
|
registry: EvidenceWitnessKeyRegistry;
|
|
12
12
|
};
|
|
13
|
+
export declare const EVIDENCE_WITNESS_BACKUP_VERIFICATION_CONTRACT: "absolutejs.vulnerability-evidence-witness-backup-verification/v1";
|
|
14
|
+
export declare const EVIDENCE_WITNESS_STATUS_CONTRACT: "absolutejs.vulnerability-evidence-witness-status/v1";
|
|
15
|
+
export type EvidenceWitnessBackupVerification = {
|
|
16
|
+
contract: typeof EVIDENCE_WITNESS_BACKUP_VERIFICATION_CONTRACT;
|
|
17
|
+
databaseArtifactDigest: `sha256:${string}`;
|
|
18
|
+
databaseRestoredAt: string;
|
|
19
|
+
signingStateArtifactDigest: `sha256:${string}`;
|
|
20
|
+
signingStateRestoredAt: string;
|
|
21
|
+
verifiedAt: string;
|
|
22
|
+
};
|
|
23
|
+
export type EvidenceWitnessStatus = {
|
|
24
|
+
backup: EvidenceWitnessBackupVerification | null;
|
|
25
|
+
checkedAt: string;
|
|
26
|
+
contract: typeof EVIDENCE_WITNESS_STATUS_CONTRACT;
|
|
27
|
+
registry: EvidenceWitnessKeyRegistry;
|
|
28
|
+
};
|
|
29
|
+
export declare const parseEvidenceWitnessBackupVerification: (value: unknown) => EvidenceWitnessBackupVerification;
|
|
13
30
|
export declare const createEvidenceWitnessService: (options: {
|
|
31
|
+
loadBackupVerification?: () => Promise<unknown | null>;
|
|
14
32
|
loadSigningState: () => Promise<string>;
|
|
15
33
|
origin: string;
|
|
16
34
|
signingState: EvidenceWitnessSigningState;
|
|
@@ -24,4 +42,5 @@ export declare const createEvidenceWitnessService: (options: {
|
|
|
24
42
|
}) => Promise<EvidenceWitnessKeyRegistry>;
|
|
25
43
|
registry: () => Promise<EvidenceWitnessKeyRegistry>;
|
|
26
44
|
rotate: (rotatedAt?: string) => Promise<EvidenceWitnessKeyRegistry>;
|
|
45
|
+
status: () => Promise<EvidenceWitnessStatus>;
|
|
27
46
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@absolutejs/vulnerabilities-witness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Independent durable transparency witness with rollback, equivocation, key-rotation, and HTTP service primitives for AbsoluteJS vulnerability evidence.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BSL-1.1",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@absolutejs/manifest": "0.3.0",
|
|
64
64
|
"@absolutejs/secrets": "0.7.2",
|
|
65
|
-
"@absolutejs/vulnerabilities": "0.
|
|
65
|
+
"@absolutejs/vulnerabilities": "0.14.0",
|
|
66
66
|
"@sinclair/typebox": "0.34.52"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|