@absolutejs/vulnerabilities-witness 0.1.1 → 0.2.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.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 }) => 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
- EVIDENCE_WITNESS_REQUEST_CONTRACT
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 }) => 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" },
@@ -406,6 +446,10 @@ var signingState = parseEvidenceWitnessSigningState(JSON.parse(encodedSigningSta
406
446
  var tokens = JSON.parse(await loadOrBootstrap(tokensName));
407
447
  var subjectsByToken = new Map(Object.entries(tokens).map(([subject, token]) => [token, subject]));
408
448
  var service2 = createEvidenceWitnessService({
449
+ loadBackupVerification: async () => {
450
+ const encoded = await secrets.fetch(backupVerificationName);
451
+ return encoded ? JSON.parse(encoded) : null;
452
+ },
409
453
  loadSigningState: async () => {
410
454
  const encoded = await secrets.fetch(signingStateName);
411
455
  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.1.1",
3
+ "version": "0.2.0",
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.13.0",
65
+ "@absolutejs/vulnerabilities": "0.14.0",
66
66
  "@sinclair/typebox": "0.34.52"
67
67
  },
68
68
  "devDependencies": {