@absolutejs/vulnerabilities-postgres 0.2.0 → 0.3.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
@@ -14,11 +14,11 @@ const osvSnapshots = persistence.snapshots();
14
14
  ```
15
15
 
16
16
  The package stores complete provider snapshots and records, appendable sync
17
- history, tenant-scoped managed findings and correlation observations, and
18
- expiring distributed refresh leases. Snapshot replacement is one Postgres
19
- statement, so readers never see a half-replaced record set. Schema creation is
20
- lazy and idempotent, or can be disabled when application migrations own the
21
- tables.
17
+ history, tenant-scoped managed findings, correlation observations, risk
18
+ assessments, and expiring distributed refresh leases. Snapshot replacement is
19
+ one Postgres statement, so readers never see a half-replaced record set. Schema
20
+ creation is lazy and idempotent, or can be disabled when application migrations
21
+ own the tables.
22
22
 
23
23
  The SQL surface is compatible with postgres.js and Neon-style tagged-template
24
24
  clients. Table prefixes are strictly validated before identifiers are included
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type FeedSnapshotStore, type FeedSyncRunStore, type ManagedFindingStore, type VulnerabilityObservationStore } from "@absolutejs/vulnerabilities";
1
+ import { type FeedSnapshotStore, type FeedSyncRunStore, type ManagedFindingStore, type VulnerabilityObservationStore, type VulnerabilityRiskAssessmentStore } from "@absolutejs/vulnerabilities";
2
2
  export type PostgresTag = {
3
3
  (strings: TemplateStringsArray, ...values: never[]): PromiseLike<unknown[]>;
4
4
  unsafe: (sql: string, ...args: never[]) => PromiseLike<unknown[]>;
@@ -18,6 +18,7 @@ export type PostgresVulnerabilityStore = {
18
18
  findings: ManagedFindingStore;
19
19
  leases: FeedLeaseStore;
20
20
  observations: VulnerabilityObservationStore;
21
+ riskAssessments: VulnerabilityRiskAssessmentStore;
21
22
  snapshots: <T>() => FeedSnapshotStore<T>;
22
23
  syncRuns: FeedSyncRunStore;
23
24
  };
package/dist/index.js CHANGED
@@ -2,7 +2,8 @@
2
2
  // src/index.ts
3
3
  import {
4
4
  ManagedVulnerabilityFindingSchema,
5
- VulnerabilityObservationSchema
5
+ VulnerabilityObservationSchema,
6
+ VulnerabilityRiskAssessmentSchema
6
7
  } from "@absolutejs/vulnerabilities";
7
8
  import { Value } from "@sinclair/typebox/value";
8
9
  var IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
@@ -58,6 +59,7 @@ var prefixTables = (prefix) => ({
58
59
  findings: `${prefix}_findings`,
59
60
  leases: `${prefix}_feed_leases`,
60
61
  observations: `${prefix}_observations`,
62
+ riskAssessments: `${prefix}_risk_assessments`,
61
63
  records: `${prefix}_feed_records`,
62
64
  runs: `${prefix}_feed_sync_runs`,
63
65
  snapshots: `${prefix}_feed_snapshots`
@@ -132,6 +134,21 @@ var vulnerabilityPostgresSchemaSql = (tablePrefix = "vulnerability") => {
132
134
  ON ${table.observations} (tenant_id, asset_id, observed_at DESC);
133
135
  CREATE INDEX IF NOT EXISTS ${table.observations}_tenant_component_idx
134
136
  ON ${table.observations} (tenant_id, component_id, observed_at DESC);
137
+ CREATE TABLE IF NOT EXISTS ${table.riskAssessments} (
138
+ tenant_id text NOT NULL,
139
+ finding_id text NOT NULL,
140
+ priority text NOT NULL,
141
+ assessed_at timestamptz NOT NULL,
142
+ remediate_by timestamptz,
143
+ value jsonb NOT NULL,
144
+ updated_at timestamptz NOT NULL DEFAULT now(),
145
+ PRIMARY KEY (tenant_id, finding_id)
146
+ );
147
+ CREATE INDEX IF NOT EXISTS ${table.riskAssessments}_tenant_priority_idx
148
+ ON ${table.riskAssessments} (tenant_id, priority, assessed_at DESC);
149
+ CREATE INDEX IF NOT EXISTS ${table.riskAssessments}_tenant_due_idx
150
+ ON ${table.riskAssessments} (tenant_id, remediate_by)
151
+ WHERE remediate_by IS NOT NULL;
135
152
  CREATE TABLE IF NOT EXISTS ${table.leases} (
136
153
  feed_id text PRIMARY KEY,
137
154
  owner_id text NOT NULL,
@@ -465,6 +482,76 @@ var createPostgresVulnerabilityStore = (options) => {
465
482
  save: async (tenantId, observation) => saveObservations(tenantId, [observation]),
466
483
  saveMany: saveObservations
467
484
  };
485
+ const parseRiskAssessment = (value) => {
486
+ const assessment = json(value, "Vulnerability risk assessment");
487
+ if (!Value.Check(VulnerabilityRiskAssessmentSchema, assessment))
488
+ throw new Error("Stored vulnerability risk assessment is invalid");
489
+ return assessment;
490
+ };
491
+ const saveRiskAssessments = async (tenantId, assessments) => {
492
+ await ensureSchema();
493
+ const normalizedTenantId = requiredText(tenantId, "Tenant id");
494
+ if (assessments.length === 0)
495
+ return;
496
+ for (const assessment of assessments) {
497
+ const findingId = assessment.findingId;
498
+ if (!Value.Check(VulnerabilityRiskAssessmentSchema, assessment))
499
+ throw new Error(`Vulnerability risk assessment ${findingId} is invalid`);
500
+ }
501
+ const payload = JSON.stringify(assessments.map((assessment) => ({
502
+ assessed_at: assessment.assessedAt,
503
+ finding_id: assessment.findingId,
504
+ priority: assessment.priority,
505
+ remediate_by: assessment.remediateBy,
506
+ value: assessment
507
+ })));
508
+ await sql`
509
+ INSERT INTO ${sql.unsafe(table.riskAssessments)} (
510
+ tenant_id, finding_id, priority, assessed_at, remediate_by, value,
511
+ updated_at
512
+ )
513
+ SELECT
514
+ ${normalizedTenantId}, item.finding_id, item.priority,
515
+ item.assessed_at::timestamptz, item.remediate_by::timestamptz,
516
+ item.value, now()
517
+ FROM jsonb_to_recordset(${payload}::jsonb) AS item(
518
+ finding_id text, priority text, assessed_at text, remediate_by text,
519
+ value jsonb
520
+ )
521
+ ON CONFLICT (tenant_id, finding_id) DO UPDATE SET
522
+ priority = EXCLUDED.priority,
523
+ assessed_at = EXCLUDED.assessed_at,
524
+ remediate_by = EXCLUDED.remediate_by,
525
+ value = EXCLUDED.value,
526
+ updated_at = now()
527
+ `;
528
+ };
529
+ const riskAssessments = {
530
+ get: async (tenantId, findingId) => {
531
+ await ensureSchema();
532
+ const rows = await sql`
533
+ SELECT value FROM ${sql.unsafe(table.riskAssessments)}
534
+ WHERE tenant_id = ${requiredText(tenantId, "Tenant id")}
535
+ AND finding_id = ${requiredText(findingId, "Finding id")}
536
+ `;
537
+ return rows[0] ? parseRiskAssessment(rows[0].value) : null;
538
+ },
539
+ list: async (filter) => {
540
+ await ensureSchema();
541
+ const tenantId = requiredText(filter.tenantId, "Tenant id");
542
+ const priority = filter.priority ?? null;
543
+ const rows = await sql`
544
+ SELECT value FROM ${sql.unsafe(table.riskAssessments)}
545
+ WHERE tenant_id = ${tenantId}
546
+ AND (${priority}::text IS NULL OR priority = ${priority})
547
+ ORDER BY assessed_at DESC, finding_id
548
+ LIMIT ${limit(filter.limit)}
549
+ `;
550
+ return rows.map(({ value }) => parseRiskAssessment(value));
551
+ },
552
+ save: async (tenantId, assessment) => saveRiskAssessments(tenantId, [assessment]),
553
+ saveMany: saveRiskAssessments
554
+ };
468
555
  const leases = {
469
556
  acquire: async (request) => {
470
557
  await ensureSchema();
@@ -507,6 +594,7 @@ var createPostgresVulnerabilityStore = (options) => {
507
594
  findings,
508
595
  leases,
509
596
  observations,
597
+ riskAssessments,
510
598
  snapshots,
511
599
  syncRuns
512
600
  };
package/dist/manifest.js CHANGED
@@ -9,7 +9,7 @@ var manifest = defineManifest()({
9
9
  intents: [
10
10
  "persist vulnerability intelligence",
11
11
  "retain vulnerability sync history",
12
- "store managed vulnerability findings and observations"
12
+ "store managed vulnerability findings, observations, and risk assessments"
13
13
  ],
14
14
  keywords: ["vulnerabilities", "Postgres", "CVE", "feed-history"],
15
15
  protocols: ["PostgreSQL", "AbsoluteJS vulnerability contracts"]
@@ -17,7 +17,7 @@ var manifest = defineManifest()({
17
17
  identity: {
18
18
  accent: "#336791",
19
19
  category: "operations",
20
- description: "Durable Postgres snapshots, records, sync history, managed findings, observations, and distributed refresh leases.",
20
+ description: "Durable Postgres snapshots, sync history, findings, observations, risk assessments, and distributed refresh leases.",
21
21
  docsUrl: "https://github.com/absolutejs/vulnerabilities-postgres",
22
22
  name: "@absolutejs/vulnerabilities-postgres",
23
23
  tagline: "Keep vulnerability intelligence durable and auditable."
@@ -30,7 +30,7 @@ var manifest = defineManifest()({
30
30
  requires: {
31
31
  env: [
32
32
  {
33
- description: "Postgres connection string for vulnerability intelligence, findings, and observations",
33
+ description: "Postgres connection string for vulnerability intelligence, findings, observations, and risk assessments",
34
34
  example: "postgres://user:pass@host/db",
35
35
  key: "DATABASE_URL",
36
36
  secret: true
@@ -45,7 +45,7 @@ var manifest = defineManifest()({
45
45
  ],
46
46
  services: [
47
47
  {
48
- description: "Stores vulnerability intelligence, history, findings, observations, and leases",
48
+ description: "Stores vulnerability intelligence, findings, observations, risk assessments, and leases",
49
49
  id: "postgres"
50
50
  }
51
51
  ]
@@ -8,7 +8,7 @@
8
8
  "intents": [
9
9
  "persist vulnerability intelligence",
10
10
  "retain vulnerability sync history",
11
- "store managed vulnerability findings and observations"
11
+ "store managed vulnerability findings, observations, and risk assessments"
12
12
  ],
13
13
  "keywords": [
14
14
  "vulnerabilities",
@@ -24,7 +24,7 @@
24
24
  "identity": {
25
25
  "accent": "#336791",
26
26
  "category": "operations",
27
- "description": "Durable Postgres snapshots, records, sync history, managed findings, observations, and distributed refresh leases.",
27
+ "description": "Durable Postgres snapshots, sync history, findings, observations, risk assessments, and distributed refresh leases.",
28
28
  "docsUrl": "https://github.com/absolutejs/vulnerabilities-postgres",
29
29
  "name": "@absolutejs/vulnerabilities-postgres",
30
30
  "tagline": "Keep vulnerability intelligence durable and auditable."
@@ -37,7 +37,7 @@
37
37
  "requires": {
38
38
  "env": [
39
39
  {
40
- "description": "Postgres connection string for vulnerability intelligence, findings, and observations",
40
+ "description": "Postgres connection string for vulnerability intelligence, findings, observations, and risk assessments",
41
41
  "example": "postgres://user:pass@host/db",
42
42
  "key": "DATABASE_URL",
43
43
  "secret": true
@@ -52,7 +52,7 @@
52
52
  ],
53
53
  "services": [
54
54
  {
55
- "description": "Stores vulnerability intelligence, history, findings, observations, and leases",
55
+ "description": "Stores vulnerability intelligence, findings, observations, risk assessments, and leases",
56
56
  "id": "postgres"
57
57
  }
58
58
  ]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@absolutejs/vulnerabilities-postgres",
3
- "version": "0.2.0",
4
- "description": "Durable Postgres feed snapshots, sync history, findings, observations, and refresh leases for AbsoluteJS vulnerability management.",
3
+ "version": "0.3.0",
4
+ "description": "Durable Postgres feeds, findings, observations, risk assessments, and refresh leases for AbsoluteJS vulnerability management.",
5
5
  "type": "module",
6
6
  "license": "BSL-1.1",
7
7
  "author": "Alex Kahn",
@@ -53,7 +53,7 @@
53
53
  ],
54
54
  "dependencies": {
55
55
  "@absolutejs/manifest": "0.3.0",
56
- "@absolutejs/vulnerabilities": "0.6.1",
56
+ "@absolutejs/vulnerabilities": "0.7.0",
57
57
  "@sinclair/typebox": "0.34.52"
58
58
  },
59
59
  "peerDependencies": {