@absolutejs/vulnerabilities-postgres 0.4.0 → 0.5.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 +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +200 -0
- package/dist/manifest.js +4 -4
- package/dist/manifest.json +4 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ const osvSnapshots = persistence.snapshots();
|
|
|
15
15
|
|
|
16
16
|
The package stores complete provider snapshots and records, appendable sync
|
|
17
17
|
history, tenant-scoped managed findings, correlation observations, VEX decisions
|
|
18
|
-
and applications, risk assessments, and expiring distributed refresh leases. Snapshot replacement is
|
|
18
|
+
and applications, remediation plans and evidence, risk assessments, and expiring distributed refresh leases. Snapshot replacement is
|
|
19
19
|
one Postgres statement, so readers never see a half-replaced record set. Schema
|
|
20
20
|
creation is lazy and idempotent, or can be disabled when application migrations
|
|
21
21
|
own the tables.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type FeedSnapshotStore, type FeedSyncRunStore, type ManagedFindingStore, type VulnerabilityObservationStore, type VulnerabilityRiskAssessmentStore, type VexDecisionStore, type VexFindingApplicationStore } from "@absolutejs/vulnerabilities";
|
|
1
|
+
import { type FeedSnapshotStore, type FeedSyncRunStore, type ManagedFindingStore, type RemediationExecutionStore, type RemediationPlanStore, type RemediationVerificationStore, type VulnerabilityObservationStore, type VulnerabilityRiskAssessmentStore, type VexDecisionStore, type VexFindingApplicationStore } 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,9 @@ export type PostgresVulnerabilityStore = {
|
|
|
18
18
|
findings: ManagedFindingStore;
|
|
19
19
|
leases: FeedLeaseStore;
|
|
20
20
|
observations: VulnerabilityObservationStore;
|
|
21
|
+
remediationExecutions: RemediationExecutionStore;
|
|
22
|
+
remediationPlans: RemediationPlanStore;
|
|
23
|
+
remediationVerifications: RemediationVerificationStore;
|
|
21
24
|
riskAssessments: VulnerabilityRiskAssessmentStore;
|
|
22
25
|
snapshots: <T>() => FeedSnapshotStore<T>;
|
|
23
26
|
syncRuns: FeedSyncRunStore;
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
// src/index.ts
|
|
3
3
|
import {
|
|
4
4
|
ManagedVulnerabilityFindingSchema,
|
|
5
|
+
RemediationExecutionSchema,
|
|
6
|
+
RemediationPlanSchema,
|
|
7
|
+
RemediationVerificationSchema,
|
|
5
8
|
VulnerabilityObservationSchema,
|
|
6
9
|
VulnerabilityRiskAssessmentSchema,
|
|
7
10
|
VexDecisionSchema,
|
|
@@ -61,6 +64,9 @@ var prefixTables = (prefix) => ({
|
|
|
61
64
|
findings: `${prefix}_findings`,
|
|
62
65
|
leases: `${prefix}_feed_leases`,
|
|
63
66
|
observations: `${prefix}_observations`,
|
|
67
|
+
remediationExecutions: `${prefix}_remediation_executions`,
|
|
68
|
+
remediationPlans: `${prefix}_remediation_plans`,
|
|
69
|
+
remediationVerifications: `${prefix}_remediation_verifications`,
|
|
64
70
|
riskAssessments: `${prefix}_risk_assessments`,
|
|
65
71
|
records: `${prefix}_feed_records`,
|
|
66
72
|
runs: `${prefix}_feed_sync_runs`,
|
|
@@ -153,6 +159,43 @@ var vulnerabilityPostgresSchemaSql = (tablePrefix = "vulnerability") => {
|
|
|
153
159
|
CREATE INDEX IF NOT EXISTS ${table.riskAssessments}_tenant_due_idx
|
|
154
160
|
ON ${table.riskAssessments} (tenant_id, remediate_by)
|
|
155
161
|
WHERE remediate_by IS NOT NULL;
|
|
162
|
+
CREATE TABLE IF NOT EXISTS ${table.remediationPlans} (
|
|
163
|
+
tenant_id text NOT NULL,
|
|
164
|
+
plan_id text NOT NULL,
|
|
165
|
+
status text NOT NULL,
|
|
166
|
+
created_at timestamptz NOT NULL,
|
|
167
|
+
value jsonb NOT NULL,
|
|
168
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
169
|
+
PRIMARY KEY (tenant_id, plan_id)
|
|
170
|
+
);
|
|
171
|
+
CREATE INDEX IF NOT EXISTS ${table.remediationPlans}_tenant_status_idx
|
|
172
|
+
ON ${table.remediationPlans} (tenant_id, status, created_at DESC);
|
|
173
|
+
CREATE TABLE IF NOT EXISTS ${table.remediationExecutions} (
|
|
174
|
+
tenant_id text NOT NULL,
|
|
175
|
+
execution_id text NOT NULL,
|
|
176
|
+
plan_id text NOT NULL,
|
|
177
|
+
status text NOT NULL,
|
|
178
|
+
started_at timestamptz NOT NULL,
|
|
179
|
+
completed_at timestamptz,
|
|
180
|
+
value jsonb NOT NULL,
|
|
181
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
182
|
+
PRIMARY KEY (tenant_id, execution_id)
|
|
183
|
+
);
|
|
184
|
+
CREATE INDEX IF NOT EXISTS ${table.remediationExecutions}_tenant_plan_idx
|
|
185
|
+
ON ${table.remediationExecutions} (tenant_id, plan_id, started_at DESC);
|
|
186
|
+
CREATE TABLE IF NOT EXISTS ${table.remediationVerifications} (
|
|
187
|
+
tenant_id text NOT NULL,
|
|
188
|
+
verification_id text NOT NULL,
|
|
189
|
+
execution_id text NOT NULL,
|
|
190
|
+
plan_id text NOT NULL,
|
|
191
|
+
status text NOT NULL,
|
|
192
|
+
observed_at timestamptz NOT NULL,
|
|
193
|
+
value jsonb NOT NULL,
|
|
194
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
195
|
+
PRIMARY KEY (tenant_id, verification_id)
|
|
196
|
+
);
|
|
197
|
+
CREATE INDEX IF NOT EXISTS ${table.remediationVerifications}_tenant_execution_idx
|
|
198
|
+
ON ${table.remediationVerifications} (tenant_id, execution_id, observed_at DESC);
|
|
156
199
|
CREATE TABLE IF NOT EXISTS ${table.vexDecisions} (
|
|
157
200
|
tenant_id text NOT NULL,
|
|
158
201
|
decision_id text NOT NULL,
|
|
@@ -584,6 +627,160 @@ var createPostgresVulnerabilityStore = (options) => {
|
|
|
584
627
|
save: async (tenantId, assessment) => saveRiskAssessments(tenantId, [assessment]),
|
|
585
628
|
saveMany: saveRiskAssessments
|
|
586
629
|
};
|
|
630
|
+
const parseRemediationPlan = (value) => {
|
|
631
|
+
const plan = json(value, "Remediation plan");
|
|
632
|
+
if (!Value.Check(RemediationPlanSchema, plan))
|
|
633
|
+
throw new Error("Stored remediation plan is invalid");
|
|
634
|
+
return plan;
|
|
635
|
+
};
|
|
636
|
+
const remediationPlans = {
|
|
637
|
+
get: async (tenantId, planId) => {
|
|
638
|
+
await ensureSchema();
|
|
639
|
+
const rows = await sql`
|
|
640
|
+
SELECT value FROM ${sql.unsafe(table.remediationPlans)}
|
|
641
|
+
WHERE tenant_id = ${requiredText(tenantId, "Tenant id")}
|
|
642
|
+
AND plan_id = ${requiredText(planId, "Remediation plan id")}
|
|
643
|
+
`;
|
|
644
|
+
return rows[0] ? parseRemediationPlan(rows[0].value) : null;
|
|
645
|
+
},
|
|
646
|
+
list: async (filter) => {
|
|
647
|
+
await ensureSchema();
|
|
648
|
+
const tenantId = requiredText(filter.tenantId, "Tenant id");
|
|
649
|
+
const status = filter.status ?? null;
|
|
650
|
+
const rows = await sql`
|
|
651
|
+
SELECT value FROM ${sql.unsafe(table.remediationPlans)}
|
|
652
|
+
WHERE tenant_id = ${tenantId}
|
|
653
|
+
AND (${status}::text IS NULL OR status = ${status})
|
|
654
|
+
ORDER BY created_at DESC, plan_id
|
|
655
|
+
LIMIT ${limit(filter.limit)}
|
|
656
|
+
`;
|
|
657
|
+
return rows.map(({ value }) => parseRemediationPlan(value));
|
|
658
|
+
},
|
|
659
|
+
save: async (tenantId, plan) => {
|
|
660
|
+
await ensureSchema();
|
|
661
|
+
const planId = plan.id;
|
|
662
|
+
if (!Value.Check(RemediationPlanSchema, plan))
|
|
663
|
+
throw new Error(`Remediation plan ${planId} is invalid`);
|
|
664
|
+
await sql`
|
|
665
|
+
INSERT INTO ${sql.unsafe(table.remediationPlans)} (
|
|
666
|
+
tenant_id, plan_id, status, created_at, value, updated_at
|
|
667
|
+
) VALUES (
|
|
668
|
+
${requiredText(tenantId, "Tenant id")}, ${plan.id}, ${plan.status},
|
|
669
|
+
${plan.createdAt}::timestamptz, ${JSON.stringify(plan)}::jsonb, now()
|
|
670
|
+
)
|
|
671
|
+
ON CONFLICT (tenant_id, plan_id) DO UPDATE SET
|
|
672
|
+
status = EXCLUDED.status,
|
|
673
|
+
created_at = EXCLUDED.created_at,
|
|
674
|
+
value = EXCLUDED.value,
|
|
675
|
+
updated_at = now()
|
|
676
|
+
`;
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
const parseRemediationExecution = (value) => {
|
|
680
|
+
const execution = json(value, "Remediation execution");
|
|
681
|
+
if (!Value.Check(RemediationExecutionSchema, execution))
|
|
682
|
+
throw new Error("Stored remediation execution is invalid");
|
|
683
|
+
return execution;
|
|
684
|
+
};
|
|
685
|
+
const remediationExecutions = {
|
|
686
|
+
get: async (tenantId, executionId) => {
|
|
687
|
+
await ensureSchema();
|
|
688
|
+
const rows = await sql`
|
|
689
|
+
SELECT value FROM ${sql.unsafe(table.remediationExecutions)}
|
|
690
|
+
WHERE tenant_id = ${requiredText(tenantId, "Tenant id")}
|
|
691
|
+
AND execution_id = ${requiredText(executionId, "Remediation execution id")}
|
|
692
|
+
`;
|
|
693
|
+
return rows[0] ? parseRemediationExecution(rows[0].value) : null;
|
|
694
|
+
},
|
|
695
|
+
list: async (tenantId, planId) => {
|
|
696
|
+
await ensureSchema();
|
|
697
|
+
const rows = await sql`
|
|
698
|
+
SELECT value FROM ${sql.unsafe(table.remediationExecutions)}
|
|
699
|
+
WHERE tenant_id = ${requiredText(tenantId, "Tenant id")}
|
|
700
|
+
AND plan_id = ${requiredText(planId, "Remediation plan id")}
|
|
701
|
+
ORDER BY started_at DESC, execution_id
|
|
702
|
+
LIMIT 1000
|
|
703
|
+
`;
|
|
704
|
+
return rows.map(({ value }) => parseRemediationExecution(value));
|
|
705
|
+
},
|
|
706
|
+
save: async (tenantId, execution) => {
|
|
707
|
+
await ensureSchema();
|
|
708
|
+
const executionId = execution.id;
|
|
709
|
+
if (!Value.Check(RemediationExecutionSchema, execution))
|
|
710
|
+
throw new Error(`Remediation execution ${executionId} is invalid`);
|
|
711
|
+
await sql`
|
|
712
|
+
INSERT INTO ${sql.unsafe(table.remediationExecutions)} (
|
|
713
|
+
tenant_id, execution_id, plan_id, status, started_at, completed_at,
|
|
714
|
+
value, updated_at
|
|
715
|
+
) VALUES (
|
|
716
|
+
${requiredText(tenantId, "Tenant id")}, ${execution.id},
|
|
717
|
+
${execution.planId}, ${execution.status},
|
|
718
|
+
${execution.startedAt}::timestamptz,
|
|
719
|
+
${execution.completedAt}::timestamptz,
|
|
720
|
+
${JSON.stringify(execution)}::jsonb, now()
|
|
721
|
+
)
|
|
722
|
+
ON CONFLICT (tenant_id, execution_id) DO UPDATE SET
|
|
723
|
+
plan_id = EXCLUDED.plan_id,
|
|
724
|
+
status = EXCLUDED.status,
|
|
725
|
+
started_at = EXCLUDED.started_at,
|
|
726
|
+
completed_at = EXCLUDED.completed_at,
|
|
727
|
+
value = EXCLUDED.value,
|
|
728
|
+
updated_at = now()
|
|
729
|
+
`;
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
const parseRemediationVerification = (value) => {
|
|
733
|
+
const verification = json(value, "Remediation verification");
|
|
734
|
+
if (!Value.Check(RemediationVerificationSchema, verification))
|
|
735
|
+
throw new Error("Stored remediation verification is invalid");
|
|
736
|
+
return verification;
|
|
737
|
+
};
|
|
738
|
+
const remediationVerifications = {
|
|
739
|
+
get: async (tenantId, verificationId) => {
|
|
740
|
+
await ensureSchema();
|
|
741
|
+
const rows = await sql`
|
|
742
|
+
SELECT value FROM ${sql.unsafe(table.remediationVerifications)}
|
|
743
|
+
WHERE tenant_id = ${requiredText(tenantId, "Tenant id")}
|
|
744
|
+
AND verification_id = ${requiredText(verificationId, "Remediation verification id")}
|
|
745
|
+
`;
|
|
746
|
+
return rows[0] ? parseRemediationVerification(rows[0].value) : null;
|
|
747
|
+
},
|
|
748
|
+
list: async (tenantId, executionId) => {
|
|
749
|
+
await ensureSchema();
|
|
750
|
+
const rows = await sql`
|
|
751
|
+
SELECT value FROM ${sql.unsafe(table.remediationVerifications)}
|
|
752
|
+
WHERE tenant_id = ${requiredText(tenantId, "Tenant id")}
|
|
753
|
+
AND execution_id = ${requiredText(executionId, "Remediation execution id")}
|
|
754
|
+
ORDER BY observed_at DESC, verification_id
|
|
755
|
+
LIMIT 1000
|
|
756
|
+
`;
|
|
757
|
+
return rows.map(({ value }) => parseRemediationVerification(value));
|
|
758
|
+
},
|
|
759
|
+
save: async (tenantId, verification) => {
|
|
760
|
+
await ensureSchema();
|
|
761
|
+
const verificationId = verification.id;
|
|
762
|
+
if (!Value.Check(RemediationVerificationSchema, verification))
|
|
763
|
+
throw new Error(`Remediation verification ${verificationId} is invalid`);
|
|
764
|
+
await sql`
|
|
765
|
+
INSERT INTO ${sql.unsafe(table.remediationVerifications)} (
|
|
766
|
+
tenant_id, verification_id, execution_id, plan_id, status,
|
|
767
|
+
observed_at, value, updated_at
|
|
768
|
+
) VALUES (
|
|
769
|
+
${requiredText(tenantId, "Tenant id")}, ${verification.id},
|
|
770
|
+
${verification.executionId}, ${verification.planId},
|
|
771
|
+
${verification.status}, ${verification.observedAt}::timestamptz,
|
|
772
|
+
${JSON.stringify(verification)}::jsonb, now()
|
|
773
|
+
)
|
|
774
|
+
ON CONFLICT (tenant_id, verification_id) DO UPDATE SET
|
|
775
|
+
execution_id = EXCLUDED.execution_id,
|
|
776
|
+
plan_id = EXCLUDED.plan_id,
|
|
777
|
+
status = EXCLUDED.status,
|
|
778
|
+
observed_at = EXCLUDED.observed_at,
|
|
779
|
+
value = EXCLUDED.value,
|
|
780
|
+
updated_at = now()
|
|
781
|
+
`;
|
|
782
|
+
}
|
|
783
|
+
};
|
|
587
784
|
const parseVexDecision = (value) => {
|
|
588
785
|
const decision = json(value, "VEX decision");
|
|
589
786
|
if (!Value.Check(VexDecisionSchema, decision))
|
|
@@ -759,6 +956,9 @@ var createPostgresVulnerabilityStore = (options) => {
|
|
|
759
956
|
findings,
|
|
760
957
|
leases,
|
|
761
958
|
observations,
|
|
959
|
+
remediationExecutions,
|
|
960
|
+
remediationPlans,
|
|
961
|
+
remediationVerifications,
|
|
762
962
|
riskAssessments,
|
|
763
963
|
snapshots,
|
|
764
964
|
syncRuns,
|
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,
|
|
12
|
+
"store managed vulnerability findings, VEX decisions, remediation evidence, 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,
|
|
20
|
+
description: "Durable Postgres snapshots, findings, VEX decisions, remediation evidence, 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,
|
|
33
|
+
description: "Postgres connection string for vulnerability intelligence, findings, VEX decisions, remediation evidence, 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, findings,
|
|
48
|
+
description: "Stores vulnerability intelligence, findings, VEX decisions, remediation evidence, risk assessments, and leases",
|
|
49
49
|
id: "postgres"
|
|
50
50
|
}
|
|
51
51
|
]
|
package/dist/manifest.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"intents": [
|
|
9
9
|
"persist vulnerability intelligence",
|
|
10
10
|
"retain vulnerability sync history",
|
|
11
|
-
"store managed vulnerability findings,
|
|
11
|
+
"store managed vulnerability findings, VEX decisions, remediation evidence, 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,
|
|
27
|
+
"description": "Durable Postgres snapshots, findings, VEX decisions, remediation evidence, 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,
|
|
40
|
+
"description": "Postgres connection string for vulnerability intelligence, findings, VEX decisions, remediation evidence, 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, findings,
|
|
55
|
+
"description": "Stores vulnerability intelligence, findings, VEX decisions, remediation evidence, 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.
|
|
4
|
-
"description": "Durable Postgres feeds, findings,
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Durable Postgres feeds, findings, VEX decisions, remediation evidence, 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.
|
|
56
|
+
"@absolutejs/vulnerabilities": "0.8.0",
|
|
57
57
|
"@sinclair/typebox": "0.34.52"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|