@pipeline-builder/pipeline-data 4.3.3 → 4.3.4
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/lib/api/crud-service.d.ts +7 -0
- package/lib/api/crud-service.js +14 -3
- package/lib/api/pipeline-step-resolution.d.ts +111 -0
- package/lib/api/pipeline-step-resolution.js +163 -0
- package/lib/api/plugin-consumption-policy.d.ts +8 -2
- package/lib/api/plugin-consumption-policy.js +20 -3
- package/lib/api/plugin-resolution.d.ts +26 -9
- package/lib/api/plugin-resolution.js +51 -33
- package/lib/api/reporting/analytics-queries.d.ts +6 -4
- package/lib/api/reporting/analytics-queries.js +11 -11
- package/lib/api/reporting/deploy-rollup.d.ts +2 -0
- package/lib/api/reporting/deploy-rollup.js +5 -1
- package/lib/api/reporting/failure-classifier.d.ts +2 -6
- package/lib/api/reporting/failure-classifier.js +22 -1
- package/lib/api/reporting/ingest.js +73 -16
- package/lib/api/reporting/plugin-exposure.d.ts +261 -15
- package/lib/api/reporting/plugin-exposure.js +423 -38
- package/lib/api/reporting/stakeholder/compose.d.ts +1 -1
- package/lib/api/reporting/stakeholder/compose.js +30 -1
- package/lib/api/reporting/stakeholder/store.d.ts +16 -2
- package/lib/api/reporting/stakeholder/store.js +17 -6
- package/lib/api/reporting/types.d.ts +80 -1
- package/lib/api/reporting/types.js +1 -1
- package/lib/api/reporting-service.d.ts +11 -4
- package/lib/api/reporting-service.js +106 -43
- package/lib/api/soft-delete-sweep.d.ts +8 -18
- package/lib/api/soft-delete-sweep.js +35 -5
- package/lib/api/version-references.d.ts +80 -0
- package/lib/api/version-references.js +135 -0
- package/lib/core/query-filters.d.ts +3 -3
- package/lib/core/query-filters.js +1 -1
- package/lib/database/drizzle-schema.d.ts +1238 -143
- package/lib/database/drizzle-schema.js +6 -3
- package/lib/database/index.d.ts +1 -1
- package/lib/database/index.js +2 -2
- package/lib/database/schema/ecosystem.d.ts +53 -0
- package/lib/database/schema/ecosystem.js +14 -3
- package/lib/database/schema/pipeline.d.ts +38 -0
- package/lib/database/schema/pipeline.js +20 -2
- package/lib/database/schema/plugin.d.ts +265 -0
- package/lib/database/schema/plugin.js +34 -3
- package/lib/database/schema/reporting-analytics.d.ts +786 -4
- package/lib/database/schema/reporting-analytics.js +111 -5
- package/lib/index.d.ts +5 -3
- package/lib/index.js +5 -3
- package/lib/testing/exports.json +24 -0
- package/package.json +4 -3
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type PluginScanFinding, type VulnAcceptance } from '@pipeline-builder/api-core';
|
|
2
|
+
import { type SQL } from 'drizzle-orm';
|
|
1
3
|
import type { ExposureSource } from '../../database/schema/reporting-analytics.js';
|
|
2
4
|
/** How far behind a resolved version is from the newest one available. */
|
|
3
5
|
export type VersionGap = 'major' | 'minor' | 'patch' | 'none';
|
|
@@ -11,8 +13,14 @@ export interface ResolvedStep {
|
|
|
11
13
|
pluginName: string;
|
|
12
14
|
declaredVersion?: string | null;
|
|
13
15
|
resolvedVersion?: string | null;
|
|
16
|
+
/** The resolved record: a `plugins` row id or a listing version id. */
|
|
17
|
+
resolvedPluginId?: string | null;
|
|
18
|
+
/** The image digest the resolved version is pinned to (null = image-less). */
|
|
19
|
+
resolvedDigest?: string | null;
|
|
14
20
|
latestVersion?: string | null;
|
|
15
21
|
withinPolicy?: boolean;
|
|
22
|
+
/** The root org of a team's pipeline (see the column). */
|
|
23
|
+
rootOrgId?: string | null;
|
|
16
24
|
}
|
|
17
25
|
/**
|
|
18
26
|
* How far `resolved` is behind `latest`.
|
|
@@ -39,8 +47,17 @@ export declare function replacePipelineResolution(pipelineId: string, steps: rea
|
|
|
39
47
|
* the instance that declares the plugin, and re-resolving each of those pipelines
|
|
40
48
|
* from scratch would make publishing cost O(pipelines) work — so only the two
|
|
41
49
|
* columns a publish can change are touched.
|
|
50
|
+
*
|
|
51
|
+
* `owner` says whose plugin it is: a listing (`publisher` handle — every org's
|
|
52
|
+
* pipelines that declare it, so the caller must be elevated), or an org's own
|
|
53
|
+
* plugin (`orgId` — that org's pipelines only: an org plugin's name is unique
|
|
54
|
+
* only within its org, so another org's same-named plugin is a different one).
|
|
42
55
|
*/
|
|
43
|
-
export declare function refreshLatestForPlugin(pluginName: string,
|
|
56
|
+
export declare function refreshLatestForPlugin(pluginName: string, owner: {
|
|
57
|
+
publisher: string;
|
|
58
|
+
} | {
|
|
59
|
+
orgId: string;
|
|
60
|
+
}, latestVersion: string): Promise<number>;
|
|
44
61
|
/** One flagged version, as the rescan reports it. */
|
|
45
62
|
export interface ExposureInput {
|
|
46
63
|
orgId: string;
|
|
@@ -53,11 +70,55 @@ export interface ExposureInput {
|
|
|
53
70
|
criticalCount: number;
|
|
54
71
|
highCount: number;
|
|
55
72
|
topFindings?: Array<Record<string, unknown>>;
|
|
73
|
+
/** Every Critical/High finding key of the version (see the column). */
|
|
74
|
+
findingKeys?: string[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Whether an exposure row is LIVE: not fixed, not dismissed as a false positive,
|
|
78
|
+
* and not under an acceptance that is still in force.
|
|
79
|
+
*
|
|
80
|
+
* The ONE predicate every reader uses (the report section, needs-attention, the
|
|
81
|
+
* org's open list). Two readers that disagree about what "exposed" means produce
|
|
82
|
+
* a report whose vulnerability panel and needs-attention list contradict each
|
|
83
|
+
* other — which is how this predicate came to exist. An acceptance LAPSES at its
|
|
84
|
+
* deadline and the row is live again; one with no deadline (which
|
|
85
|
+
* `triageExposure` refuses to write) is treated as in force, as every reader
|
|
86
|
+
* already did.
|
|
87
|
+
*
|
|
88
|
+
* `alias` is the table name or alias the caller's query uses.
|
|
89
|
+
*/
|
|
90
|
+
export declare function liveExposurePredicate(now: Date, alias?: string): SQL;
|
|
91
|
+
/** What a version's scan state contributes to an exposure (see {@link exposureFromScan}). */
|
|
92
|
+
export interface ScanState {
|
|
93
|
+
/** The digest the step runs. A scan summary describing another image is ignored. */
|
|
94
|
+
imageDigest?: string | null;
|
|
95
|
+
scanSummary?: unknown;
|
|
96
|
+
scanFlag?: unknown;
|
|
97
|
+
scanFlaggedAt?: Date | string | null;
|
|
98
|
+
}
|
|
99
|
+
/** The counts an exposure row carries. */
|
|
100
|
+
export interface ExposureCounts {
|
|
101
|
+
criticalCount: number;
|
|
102
|
+
highCount: number;
|
|
103
|
+
topFindings: PluginScanFinding[];
|
|
104
|
+
/** Keys of EVERY Critical/High finding itemized (fixable or not), sorted. */
|
|
105
|
+
findingKeys: string[];
|
|
56
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* The exposure a version's scan state amounts to, or null when it has none.
|
|
109
|
+
*
|
|
110
|
+
* Reads the rescan's `scan_summary` (fixable Critical AND High, whether or not the
|
|
111
|
+
* gate tripped) and falls back to `scan_flag` for a version flagged before any
|
|
112
|
+
* summary was written. A summary whose digest is not the step's is IGNORED: a
|
|
113
|
+
* version rebuilt on a patched base is a different artifact, and its findings say
|
|
114
|
+
* nothing about the image the pipeline runs. Only FIXABLE findings count — an
|
|
115
|
+
* exposure is something a rebuild or an upgrade can clear.
|
|
116
|
+
*/
|
|
117
|
+
export declare function exposureFromScan(state: ScanState): ExposureCounts | null;
|
|
57
118
|
/**
|
|
58
119
|
* Open or refresh exposures.
|
|
59
120
|
*
|
|
60
|
-
* Upserts on (pipeline, plugin, version, source) so a nightly rescan REFRESHES the
|
|
121
|
+
* Upserts on (pipeline, plugin, version, digest, source) so a nightly rescan REFRESHES the
|
|
61
122
|
* counts rather than opening a new row every night — otherwise the report would
|
|
62
123
|
* show one exposure per night of the same problem, which reads as an escalating
|
|
63
124
|
* situation when nothing changed.
|
|
@@ -68,18 +129,117 @@ export interface ExposureInput {
|
|
|
68
129
|
* acceptance, because that is what the deadline was for.
|
|
69
130
|
*/
|
|
70
131
|
export declare function upsertExposures(inputs: readonly ExposureInput[], now?: Date): Promise<number>;
|
|
132
|
+
/** One version a pipeline still declares / runs WITH open findings. */
|
|
133
|
+
export interface LiveVersion {
|
|
134
|
+
pluginName: string;
|
|
135
|
+
pluginVersion: string;
|
|
136
|
+
imageDigest?: string | null;
|
|
137
|
+
}
|
|
71
138
|
/**
|
|
72
|
-
* Close the exposures a pipeline has moved off
|
|
139
|
+
* Close the exposures of one SOURCE a pipeline has moved off — `fixed` when the
|
|
140
|
+
* pipeline still uses the plugin at another version, `removed` when no live step
|
|
141
|
+
* uses the plugin at all (the triage fields stay, so a re-added step gets an
|
|
142
|
+
* unexpired acceptance back — see {@link upsertExposures}).
|
|
73
143
|
*
|
|
74
|
-
* Called when a deploy records a new step manifest
|
|
75
|
-
*
|
|
144
|
+
* Called when a deploy records a new step manifest (`deployed`) or a pipeline
|
|
145
|
+
* write re-resolves its config (`declared`): anything open for that pipeline and
|
|
146
|
+
* source whose (version, digest) is NOT in `liveVersions` is fixed. Closing by
|
|
76
147
|
* absence rather than waiting for a rescan to notice is what makes "we fixed it"
|
|
77
148
|
* show up in the report on the next deploy instead of the next night.
|
|
149
|
+
*
|
|
150
|
+
* Per source, because the two sides move independently: a config updated to the
|
|
151
|
+
* fixed version closes the DECLARED exposure while the old image keeps running,
|
|
152
|
+
* and its DEPLOYED exposure must stay open until the deploy lands.
|
|
153
|
+
*/
|
|
154
|
+
export declare function closeFixedExposures(pipelineId: string, source: ExposureSource, liveVersions: readonly LiveVersion[], now?: Date, opts?: {
|
|
155
|
+
livePlugins?: readonly string[];
|
|
156
|
+
}): Promise<number>;
|
|
157
|
+
/**
|
|
158
|
+
* Close every open exposure of a pipeline (or of one source) as `removed` — the
|
|
159
|
+
* pipeline was deleted, or deregistered (its deploy is gone). The triage fields
|
|
160
|
+
* stay: a restore re-records the pipeline's resolution, and
|
|
161
|
+
* {@link upsertExposures} reopens the rows (restoring an acceptance still in
|
|
162
|
+
* force). Returns the rows closed.
|
|
163
|
+
*/
|
|
164
|
+
export declare function closePipelineExposures(pipelineId: string, state: 'removed', opts?: {
|
|
165
|
+
source?: ExposureSource;
|
|
166
|
+
}, now?: Date): Promise<number>;
|
|
167
|
+
/** One step of a pipeline as {@link reconcilePipelineExposures} takes it. */
|
|
168
|
+
export interface ExposureStep {
|
|
169
|
+
pluginPublisher?: string | null;
|
|
170
|
+
pluginName: string;
|
|
171
|
+
pluginVersion: string;
|
|
172
|
+
imageDigest?: string | null;
|
|
173
|
+
/** The resolved version's scan state, as stored on its row. */
|
|
174
|
+
scan: ScanState;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Bring one pipeline's exposures of one source in line with the steps it now
|
|
178
|
+
* declares (`declared`) or runs (`deployed`): open / refresh a row for every step
|
|
179
|
+
* whose version has open fixable findings, close the rest by absence.
|
|
180
|
+
*
|
|
181
|
+
* Never throws — exposure tracking is reporting, and a pipeline write or a deploy
|
|
182
|
+
* registration must not fail because the report table could not be written.
|
|
78
183
|
*/
|
|
79
|
-
export declare function
|
|
184
|
+
export declare function reconcilePipelineExposures(input: {
|
|
185
|
+
orgId: string;
|
|
186
|
+
pipelineId: string;
|
|
187
|
+
source: ExposureSource;
|
|
188
|
+
steps: readonly ExposureStep[];
|
|
189
|
+
}, now?: Date): Promise<{
|
|
190
|
+
opened: number;
|
|
191
|
+
closed: number;
|
|
192
|
+
}>;
|
|
193
|
+
/** A plugin version (optionally one image of it) whose pipelines are wanted. */
|
|
194
|
+
export interface PluginVersionRef {
|
|
80
195
|
pluginName: string;
|
|
81
196
|
pluginVersion: string;
|
|
82
|
-
|
|
197
|
+
/** Listing publisher handle; null = an org's own plugin row; omitted = either. */
|
|
198
|
+
pluginPublisher?: string | null;
|
|
199
|
+
/** Narrow to pipelines on this image. Omitted = any image of the version. */
|
|
200
|
+
imageDigest?: string | null;
|
|
201
|
+
/** Narrow to these orgs. Omitted = every org the caller's scope can read. */
|
|
202
|
+
orgIds?: readonly string[];
|
|
203
|
+
}
|
|
204
|
+
/** A pipeline that declares or runs a given plugin version. */
|
|
205
|
+
export interface PipelineOnVersion {
|
|
206
|
+
orgId: string;
|
|
207
|
+
pipelineId: string;
|
|
208
|
+
pipelineName: string | null;
|
|
209
|
+
/** `deployed` = its last deploy's step manifest; `declared` = its config resolves to it. */
|
|
210
|
+
source: ExposureSource;
|
|
211
|
+
pluginPublisher: string | null;
|
|
212
|
+
imageDigest: string | null;
|
|
213
|
+
/** The steps on it: `synth` or `<stage>/<step>` (declared) / `<stage>/<action>` (deployed). */
|
|
214
|
+
steps: string[];
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Every LIVE pipeline declaring (`pipeline_plugin_resolution`) or running
|
|
218
|
+
* (`pipeline_step_manifests`) a plugin version, one entry per (pipeline, source)
|
|
219
|
+
* with the steps on it. A soft-deleted pipeline is NOT affected: counting it
|
|
220
|
+
* would put a pipeline nobody can open into a notice or a report. Reads under
|
|
221
|
+
* the CALLER's tenant scope: an org-scoped caller sees its own pipelines, the
|
|
222
|
+
* rescan (elevated) sees every org's.
|
|
223
|
+
*/
|
|
224
|
+
export declare function pipelinesOnVersion(ref: PluginVersionRef): Promise<PipelineOnVersion[]>;
|
|
225
|
+
/**
|
|
226
|
+
* The rescan's entry point: one rescanned version's findings, fanned out to every
|
|
227
|
+
* pipeline that declares or runs it.
|
|
228
|
+
*
|
|
229
|
+
* A version with open fixable findings opens / refreshes one exposure per
|
|
230
|
+
* (pipeline, source); a version that came back CLEAN closes every open exposure
|
|
231
|
+
* on it (`fixed`), so a rebuild-in-place or an advisory withdrawn upstream shows
|
|
232
|
+
* as fixed on the next night rather than lingering until each pipeline redeploys.
|
|
233
|
+
*
|
|
234
|
+
* Must run with a scope that can read and write every org's rows (the rescan's
|
|
235
|
+
* elevated context). Never throws.
|
|
236
|
+
*/
|
|
237
|
+
export declare function syncVersionExposures(ref: PluginVersionRef, scan: ScanState, now?: Date): Promise<{
|
|
238
|
+
opened: number;
|
|
239
|
+
closed: number;
|
|
240
|
+
}>;
|
|
241
|
+
/** Close every open exposure on a version (optionally one image of it). */
|
|
242
|
+
export declare function closeVersionExposures(ref: PluginVersionRef, now?: Date): Promise<number>;
|
|
83
243
|
/**
|
|
84
244
|
* The org's live exposures, for the report section.
|
|
85
245
|
*
|
|
@@ -99,20 +259,106 @@ export declare function openExposures(orgId: string): Promise<Array<{
|
|
|
99
259
|
triageState: string;
|
|
100
260
|
flaggedAt: Date;
|
|
101
261
|
}>>;
|
|
262
|
+
/** One exposure row as the per-pipeline security views read it. */
|
|
263
|
+
export interface PipelineExposure {
|
|
264
|
+
id: string;
|
|
265
|
+
orgId: string;
|
|
266
|
+
pipelineId: string;
|
|
267
|
+
pluginPublisher: string | null;
|
|
268
|
+
pluginName: string;
|
|
269
|
+
pluginVersion: string;
|
|
270
|
+
imageDigest: string | null;
|
|
271
|
+
source: ExposureSource;
|
|
272
|
+
criticalCount: number;
|
|
273
|
+
highCount: number;
|
|
274
|
+
topFindings: Array<Record<string, unknown>>;
|
|
275
|
+
findingKeys: string[];
|
|
276
|
+
/** As stored; an `accepted` row past its deadline reads `open` (see {@link liveExposurePredicate}). */
|
|
277
|
+
triageState: string;
|
|
278
|
+
/** An acceptance still in force right now. */
|
|
279
|
+
acceptanceActive: boolean;
|
|
280
|
+
acceptedUntil: Date | null;
|
|
281
|
+
acceptedFindingKeys: string[];
|
|
282
|
+
acceptedKev: boolean;
|
|
283
|
+
triageReason: string | null;
|
|
284
|
+
triagedBy: string | null;
|
|
285
|
+
triagedAt: Date | null;
|
|
286
|
+
flaggedAt: Date;
|
|
287
|
+
fixedAt: Date | null;
|
|
288
|
+
}
|
|
102
289
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* how a finding stops being anybody's problem, and there is no good reason to
|
|
107
|
-
* support it — the caller gets an error rather than a silently unbounded
|
|
108
|
-
* acceptance.
|
|
290
|
+
* One pipeline's exposures: the unfixed ones (open, accepted, false positive),
|
|
291
|
+
* plus the fixed ones when asked — most severe first. Reads under the CALLER's
|
|
292
|
+
* scope, narrowed to `orgId`.
|
|
109
293
|
*/
|
|
110
|
-
export declare function
|
|
294
|
+
export declare function pipelineExposures(orgId: string, pipelineId: string, opts?: {
|
|
295
|
+
includeFixed?: boolean;
|
|
296
|
+
now?: Date;
|
|
297
|
+
}): Promise<PipelineExposure[]>;
|
|
298
|
+
/** One exposure row of `orgId`, or null. */
|
|
299
|
+
export declare function exposureById(orgId: string, id: string, now?: Date): Promise<PipelineExposure | null>;
|
|
300
|
+
/** A triage decision. */
|
|
301
|
+
export interface TriageDecision {
|
|
111
302
|
state: 'open' | 'accepted' | 'false_positive';
|
|
112
303
|
reason?: string;
|
|
113
304
|
acceptedUntil?: Date;
|
|
305
|
+
/** Accept KEV-listed findings too. Never implied: an acceptance without it does not cover them. */
|
|
306
|
+
kev?: boolean;
|
|
114
307
|
actor: string;
|
|
115
|
-
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Record a triage decision on one exposure — and on its SIBLINGS: the same
|
|
311
|
+
* pipeline's other open row for the same version and image (the declared and
|
|
312
|
+
* the deployed side). A risk decision is about what the pipeline runs, and
|
|
313
|
+
* leaving one side open would have the report and the lookup disagree.
|
|
314
|
+
*
|
|
315
|
+
* `accepted` REQUIRES a deadline and a reason. A permanent, unexplained mute is
|
|
316
|
+
* how a finding stops being anybody's problem, and there is no good reason to
|
|
317
|
+
* support it — the caller gets an error rather than a silently unbounded
|
|
318
|
+
* acceptance. The accepted finding keys are FROZEN at the decision: an
|
|
319
|
+
* acceptance covers what was there when it was made, never a finding since.
|
|
320
|
+
*
|
|
321
|
+
* Returns the updated rows (empty when `id` is not `orgId`'s unfixed exposure).
|
|
322
|
+
*/
|
|
323
|
+
export declare function triageExposure(orgId: string, id: string, decision: TriageDecision): Promise<PipelineExposure[]>;
|
|
324
|
+
/** `name@version@digest` — the key {@link activeAcceptances} answers under. */
|
|
325
|
+
export declare const acceptanceKey: (pluginName: string, pluginVersion: string, imageDigest?: string | null) => string;
|
|
326
|
+
/** A pipeline's acceptances in force, keyed by {@link acceptanceKey}, plus the accepted versions per plugin. */
|
|
327
|
+
export interface PipelineAcceptances {
|
|
328
|
+
byKey: Map<string, VulnAcceptance & {
|
|
329
|
+
exposureIds: string[];
|
|
330
|
+
}>;
|
|
331
|
+
/** `pluginName` → versions with an acceptance (the resolver's `acceptedVersions` hint). */
|
|
332
|
+
versionsByPlugin: Map<string, Set<string>>;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* The acceptances IN FORCE for one pipeline (`accepted`, unfixed, deadline in the
|
|
336
|
+
* future), merged per version and image: the union of the finding keys the rows
|
|
337
|
+
* froze, KEV only when every merged row accepted it. Reads under the caller's
|
|
338
|
+
* scope narrowed to `orgId`; a failed read is "no acceptance" — the stricter answer.
|
|
339
|
+
*/
|
|
340
|
+
export declare function activeAcceptances(orgId: string, pipelineId: string, now?: Date): Promise<PipelineAcceptances>;
|
|
341
|
+
/** An acceptance that lapsed, as {@link reopenLapsedAcceptances} returns it. */
|
|
342
|
+
export interface LapsedAcceptance {
|
|
343
|
+
id: string;
|
|
344
|
+
orgId: string;
|
|
345
|
+
pipelineId: string;
|
|
346
|
+
pluginPublisher: string | null;
|
|
347
|
+
pluginName: string;
|
|
348
|
+
pluginVersion: string;
|
|
349
|
+
imageDigest: string | null;
|
|
350
|
+
triagedBy: string | null;
|
|
351
|
+
triageReason: string | null;
|
|
352
|
+
acceptedUntil: Date | null;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Reopen every acceptance whose deadline has passed, and return them so the
|
|
356
|
+
* pipeline owners can be told (N33). Readers already treat such a row as open;
|
|
357
|
+
* writing it makes the stored state the truth and gives the notice exactly one
|
|
358
|
+
* moment to fire. Must run with a scope that can write every org's rows (the
|
|
359
|
+
* nightly rescan's elevated context). Never throws.
|
|
360
|
+
*/
|
|
361
|
+
export declare function reopenLapsedAcceptances(now?: Date): Promise<LapsedAcceptance[]>;
|
|
116
362
|
/**
|
|
117
363
|
* Exposures whose acceptance has lapsed, for the org's security notifications.
|
|
118
364
|
*
|