@rawdash/connector-wiz 0.26.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/dist/index.js ADDED
@@ -0,0 +1,727 @@
1
+ // ../../connector-shared/dist/index.js
2
+ var HTTP_CLIENT_VERSION = "0.0.0";
3
+ var DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;
4
+ function connectorUserAgent(connectorId) {
5
+ return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;
6
+ }
7
+
8
+ // src/wiz.ts
9
+ import {
10
+ BaseConnector,
11
+ defineConfigFields,
12
+ defineConnectorDoc,
13
+ defineResources,
14
+ makeChunkedCursorGuard,
15
+ paginateChunked,
16
+ schemasFromResources,
17
+ selectActivePhases
18
+ } from "@rawdash/core";
19
+ import { z } from "zod";
20
+ var configFields = defineConfigFields(
21
+ z.object({
22
+ apiEndpoint: z.string().url().regex(
23
+ /^https:\/\/api\.[a-z0-9-]+\.app\.wiz\.io\/graphql$/i,
24
+ 'Wiz GraphQL endpoint, e.g. "https://api.us1.app.wiz.io/graphql".'
25
+ ).meta({
26
+ label: "GraphQL API endpoint",
27
+ description: 'Tenant-specific Wiz GraphQL endpoint shown on the Wiz service-account page (e.g. "https://api.us1.app.wiz.io/graphql"). The region segment changes per data residency.',
28
+ placeholder: "https://api.us1.app.wiz.io/graphql"
29
+ }),
30
+ clientId: z.string().min(1).meta({
31
+ label: "Service-account client ID",
32
+ description: "Client ID of the Wiz service account authorized for the API.",
33
+ placeholder: "aaaa-bbbb-cccc-dddd"
34
+ }),
35
+ clientSecret: z.object({ $secret: z.string().min(1) }).meta({
36
+ label: "Service-account client secret",
37
+ description: "Client secret of the Wiz service account. Stored as a secret.",
38
+ placeholder: "WIZ_CLIENT_SECRET",
39
+ secret: true
40
+ }),
41
+ tokenEndpoint: z.string().url().optional().meta({
42
+ label: "OAuth token endpoint (optional)",
43
+ description: "Override the OAuth 2.0 token endpoint. Defaults to https://auth.app.wiz.io/oauth/token; use the gov / fed equivalent for non-commercial deployments.",
44
+ placeholder: "https://auth.app.wiz.io/oauth/token"
45
+ }),
46
+ audience: z.string().min(1).optional().meta({
47
+ label: "OAuth audience (optional)",
48
+ description: 'OAuth audience claim requested when minting the access token. Defaults to "wiz-api"; some legacy tenants require "beyond-api".',
49
+ placeholder: "wiz-api"
50
+ }),
51
+ resources: z.array(z.enum(["issues", "issue_events", "vulnerabilities"])).nonempty().optional().meta({
52
+ label: "Resources",
53
+ description: "Which Wiz resources to sync. Omit to sync all of them. The issues and issue_events resources share the same underlying GraphQL query."
54
+ })
55
+ })
56
+ );
57
+ var doc = defineConnectorDoc({
58
+ displayName: "Wiz",
59
+ category: "security",
60
+ brandColor: "#11253E",
61
+ tagline: "Sync cloud-security issues, issue lifecycle events, and vulnerability findings from a Wiz tenant for open-critical, MTTR, and posture dashboards.",
62
+ vendor: {
63
+ name: "Wiz",
64
+ domain: "wiz.io",
65
+ apiDocs: "https://win.wiz.io/reference/welcome",
66
+ website: "https://wiz.io"
67
+ },
68
+ auth: {
69
+ summary: "OAuth 2.0 client-credentials flow against a Wiz service account. The connector mints an access token, refreshes it on expiry, and sends it as a Bearer header on every GraphQL request.",
70
+ setup: [
71
+ "In the Wiz portal, open Settings -> Service Accounts and create a new service account.",
72
+ "Grant it the read scopes for the resources you intend to sync (typically read:issues and read:vulnerabilities).",
73
+ "Copy the Client ID, Client Secret, and Token Endpoint shown on the service-account page.",
74
+ 'Copy the GraphQL API endpoint shown on the same page (e.g. "https://api.us1.app.wiz.io/graphql"); the region segment is tenant-specific.',
75
+ 'Store the client secret as a rawdash secret and reference it from the connector config as `clientSecret: secret("WIZ_CLIENT_SECRET")`.'
76
+ ]
77
+ },
78
+ limitations: [
79
+ "Issue lifecycle events are derived from each issue's createdAt / resolvedAt timestamps, not from a dedicated audit-log endpoint, so administrative reopen / re-resolve transitions inside the same sync window are collapsed to the latest state.",
80
+ "Service-account auth only; per-user OAuth is out of scope.",
81
+ "Cloud-configuration and threat-detection issues are returned by the same /issues query and are not segmented at the connector layer; filter on the `issueType` attribute downstream."
82
+ ]
83
+ });
84
+ var wizCredentials = {
85
+ clientId: {
86
+ description: "Wiz service-account client ID",
87
+ auth: "required"
88
+ },
89
+ clientSecret: {
90
+ description: "Wiz service-account client secret",
91
+ auth: "required"
92
+ }
93
+ };
94
+ var PHASE_ORDER = ["issues", "vulnerabilities"];
95
+ var isWizSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);
96
+ var ISSUE_ENTITY = "wiz_issue";
97
+ var ISSUE_EVENT = "wiz_issue_event";
98
+ var VULNERABILITY_ENTITY = "wiz_vulnerability";
99
+ var DEFAULT_TOKEN_ENDPOINT = "https://auth.app.wiz.io/oauth/token";
100
+ var DEFAULT_AUDIENCE = "wiz-api";
101
+ var PAGE_SIZE = 100;
102
+ var TOKEN_EXPIRY_GRACE_S = 60;
103
+ var SEVERITIES = [
104
+ "CRITICAL",
105
+ "HIGH",
106
+ "MEDIUM",
107
+ "LOW",
108
+ "INFORMATIONAL"
109
+ ];
110
+ var ISSUE_STATUSES = ["OPEN", "IN_PROGRESS", "RESOLVED", "REJECTED"];
111
+ var VULN_STATUSES = ["OPEN", "RESOLVED", "IGNORED", "IN_PROGRESS"];
112
+ var ISSUE_EVENT_KINDS = ["opened", "resolved"];
113
+ var idString = z.string().min(1);
114
+ var isoString = z.string().min(1);
115
+ var oauthTokenSchema = z.object({
116
+ access_token: z.string().min(1),
117
+ token_type: z.string().optional(),
118
+ expires_in: z.number().optional()
119
+ });
120
+ var entitySnapshotSchema = z.object({
121
+ id: idString.nullable().optional(),
122
+ name: z.string().nullable().optional(),
123
+ type: z.string().nullable().optional(),
124
+ cloudProvider: z.string().nullable().optional(),
125
+ externalId: z.string().nullable().optional()
126
+ });
127
+ var sourceRuleSchema = z.object({
128
+ id: idString.nullable().optional(),
129
+ name: z.string().nullable().optional()
130
+ });
131
+ var issueSchema = z.object({
132
+ id: idString,
133
+ severity: z.string(),
134
+ status: z.string(),
135
+ type: z.string().nullable().optional(),
136
+ resolutionReason: z.string().nullable().optional(),
137
+ createdAt: isoString,
138
+ updatedAt: isoString.nullable().optional(),
139
+ resolvedAt: isoString.nullable().optional(),
140
+ dueAt: isoString.nullable().optional(),
141
+ sourceRule: sourceRuleSchema.nullable().optional(),
142
+ entitySnapshot: entitySnapshotSchema.nullable().optional()
143
+ });
144
+ var issuesArraySchema = z.array(issueSchema);
145
+ var vulnerableAssetSchema = z.object({
146
+ id: idString.nullable().optional(),
147
+ name: z.string().nullable().optional(),
148
+ type: z.string().nullable().optional(),
149
+ cloudPlatform: z.string().nullable().optional()
150
+ });
151
+ var vulnerabilitySchema = z.object({
152
+ id: idString,
153
+ name: z.string().nullable().optional(),
154
+ severity: z.string(),
155
+ status: z.string(),
156
+ vulnerabilityExternalId: z.string().nullable().optional(),
157
+ firstDetectedAt: isoString.nullable().optional(),
158
+ lastDetectedAt: isoString.nullable().optional(),
159
+ resolvedAt: isoString.nullable().optional(),
160
+ vulnerableAsset: vulnerableAssetSchema.nullable().optional()
161
+ });
162
+ var vulnerabilitiesArraySchema = z.array(vulnerabilitySchema);
163
+ var wizResources = defineResources({
164
+ [ISSUE_ENTITY]: {
165
+ shape: "entity",
166
+ filterable: [
167
+ {
168
+ field: "severity",
169
+ ops: ["eq"],
170
+ values: [...SEVERITIES]
171
+ },
172
+ {
173
+ field: "status",
174
+ ops: ["eq"],
175
+ values: [...ISSUE_STATUSES]
176
+ },
177
+ { field: "cloudProvider", ops: ["eq"] },
178
+ { field: "resourceType", ops: ["eq"] }
179
+ ],
180
+ description: "Wiz issues (cloud-configuration, toxic-combination, and threat-detection findings) keyed by issue id, with severity, status, the offending entity snapshot, and lifecycle timestamps.",
181
+ endpoint: "GraphQL query: issues { nodes { ... } }",
182
+ notes: "Paginated via the GraphQL connection cursor; incremental syncs filter on updatedAt.after and stop once a page is entirely older than options.since.",
183
+ fields: [
184
+ {
185
+ name: "severity",
186
+ description: "CRITICAL, HIGH, MEDIUM, LOW, or INFORMATIONAL."
187
+ },
188
+ { name: "status", description: "OPEN, IN_PROGRESS, RESOLVED, REJECTED." },
189
+ {
190
+ name: "issueType",
191
+ description: "Issue category (e.g. CLOUD_CONFIGURATION, TOXIC_COMBINATION)."
192
+ },
193
+ {
194
+ name: "ruleName",
195
+ description: "Name of the source rule that produced the issue."
196
+ },
197
+ {
198
+ name: "resourceName",
199
+ description: "Name of the cloud resource the issue applies to."
200
+ },
201
+ {
202
+ name: "resourceType",
203
+ description: "Type of the cloud resource (e.g. EC2_INSTANCE, S3_BUCKET)."
204
+ },
205
+ {
206
+ name: "cloudProvider",
207
+ description: "AWS, GCP, AZURE, etc."
208
+ },
209
+ {
210
+ name: "createdAt",
211
+ description: "When Wiz first opened the issue (Unix ms)."
212
+ },
213
+ {
214
+ name: "resolvedAt",
215
+ description: "When the issue was resolved (Unix ms; null if open)."
216
+ },
217
+ {
218
+ name: "dueAt",
219
+ description: "Remediation due date as configured by SLA (Unix ms)."
220
+ }
221
+ ],
222
+ responses: {
223
+ oauth_token: oauthTokenSchema,
224
+ issues: issuesArraySchema
225
+ }
226
+ },
227
+ [ISSUE_EVENT]: {
228
+ shape: "event",
229
+ filterable: [
230
+ {
231
+ field: "kind",
232
+ ops: ["eq"],
233
+ values: [...ISSUE_EVENT_KINDS]
234
+ },
235
+ {
236
+ field: "severity",
237
+ ops: ["eq"],
238
+ values: [...SEVERITIES]
239
+ }
240
+ ],
241
+ description: 'Issue lifecycle events derived from each Wiz issue: one event at createdAt (kind="opened") and, when present, one at resolvedAt (kind="resolved"). Used to build open-rate, resolution-rate, and MTTR widgets.',
242
+ endpoint: "GraphQL query: issues { nodes { ... } } (derived)",
243
+ notes: "Events are derived from the same issues GraphQL query; enabling issue_events without issues still triggers the query but skips the entity write.",
244
+ fields: [
245
+ {
246
+ name: "kind",
247
+ description: '"opened" or "resolved".'
248
+ },
249
+ {
250
+ name: "issueId",
251
+ description: "The Wiz issue id this lifecycle event belongs to."
252
+ },
253
+ {
254
+ name: "severity",
255
+ description: "Severity of the originating issue at sync time."
256
+ },
257
+ {
258
+ name: "cloudProvider",
259
+ description: "Cloud provider of the affected resource."
260
+ }
261
+ ]
262
+ },
263
+ [VULNERABILITY_ENTITY]: {
264
+ shape: "entity",
265
+ filterable: [
266
+ {
267
+ field: "severity",
268
+ ops: ["eq"],
269
+ values: [...SEVERITIES]
270
+ },
271
+ {
272
+ field: "status",
273
+ ops: ["eq"],
274
+ values: [...VULN_STATUSES]
275
+ },
276
+ { field: "cloudPlatform", ops: ["eq"] }
277
+ ],
278
+ description: "Wiz vulnerability findings keyed by finding id, with CVE id, severity, status, first / last detection timestamps, and the affected asset.",
279
+ endpoint: "GraphQL query: vulnerabilityFindings { nodes { ... } }",
280
+ notes: "Paginated via the GraphQL connection cursor; incremental syncs filter on lastDetectedAt.after.",
281
+ fields: [
282
+ {
283
+ name: "severity",
284
+ description: "CRITICAL, HIGH, MEDIUM, LOW, or INFORMATIONAL."
285
+ },
286
+ { name: "status", description: "OPEN, RESOLVED, IGNORED, IN_PROGRESS." },
287
+ {
288
+ name: "name",
289
+ description: "Vulnerability name as reported by Wiz."
290
+ },
291
+ {
292
+ name: "cve",
293
+ description: "Vulnerability external id, typically a CVE identifier."
294
+ },
295
+ {
296
+ name: "assetName",
297
+ description: "Name of the affected asset."
298
+ },
299
+ {
300
+ name: "assetType",
301
+ description: "Type of the affected asset."
302
+ },
303
+ {
304
+ name: "cloudPlatform",
305
+ description: "Cloud platform hosting the affected asset."
306
+ },
307
+ {
308
+ name: "firstDetectedAt",
309
+ description: "When the vulnerability was first detected (Unix ms)."
310
+ },
311
+ {
312
+ name: "lastDetectedAt",
313
+ description: "When the vulnerability was last detected (Unix ms)."
314
+ },
315
+ {
316
+ name: "resolvedAt",
317
+ description: "When the vulnerability was resolved (Unix ms; null if open)."
318
+ }
319
+ ],
320
+ responses: {
321
+ vulnerabilities: vulnerabilitiesArraySchema
322
+ }
323
+ }
324
+ });
325
+ var id = "wiz";
326
+ var ISSUES_QUERY = `
327
+ query Issues($first: Int!, $after: String, $filterBy: IssueFilters, $orderBy: IssueOrder) {
328
+ issues(first: $first, after: $after, filterBy: $filterBy, orderBy: $orderBy) {
329
+ nodes {
330
+ id
331
+ severity
332
+ status
333
+ type
334
+ resolutionReason
335
+ createdAt
336
+ updatedAt
337
+ resolvedAt
338
+ dueAt
339
+ sourceRule { id name }
340
+ entitySnapshot { id name type cloudProvider externalId }
341
+ }
342
+ pageInfo { hasNextPage endCursor }
343
+ }
344
+ }
345
+ `;
346
+ var VULNERABILITIES_QUERY = `
347
+ query VulnerabilityFindings(
348
+ $first: Int!
349
+ $after: String
350
+ $filterBy: VulnerabilityFindingFilters
351
+ ) {
352
+ vulnerabilityFindings(first: $first, after: $after, filterBy: $filterBy) {
353
+ nodes {
354
+ id
355
+ name
356
+ severity
357
+ status
358
+ vulnerabilityExternalId
359
+ firstDetectedAt
360
+ lastDetectedAt
361
+ resolvedAt
362
+ vulnerableAsset { id name type cloudPlatform }
363
+ }
364
+ pageInfo { hasNextPage endCursor }
365
+ }
366
+ }
367
+ `;
368
+ function parseIsoMs(value) {
369
+ if (!value) {
370
+ return null;
371
+ }
372
+ const ms = Date.parse(value);
373
+ return Number.isFinite(ms) ? ms : null;
374
+ }
375
+ function normalizeSeverity(value) {
376
+ const upper = value.toUpperCase();
377
+ return SEVERITIES.includes(upper) ? upper : value;
378
+ }
379
+ function normalizeIssueStatus(value) {
380
+ const upper = value.toUpperCase();
381
+ return ISSUE_STATUSES.includes(upper) ? upper : value;
382
+ }
383
+ function normalizeVulnStatus(value) {
384
+ const upper = value.toUpperCase();
385
+ return VULN_STATUSES.includes(upper) ? upper : value;
386
+ }
387
+ var WizConnector = class _WizConnector extends BaseConnector {
388
+ static id = id;
389
+ static resources = wizResources;
390
+ static schemas = schemasFromResources(wizResources);
391
+ static create(input, ctx) {
392
+ const parsed = configFields.parse(input);
393
+ return new _WizConnector(
394
+ {
395
+ apiEndpoint: parsed.apiEndpoint,
396
+ tokenEndpoint: parsed.tokenEndpoint,
397
+ audience: parsed.audience,
398
+ resources: parsed.resources
399
+ },
400
+ {
401
+ clientId: parsed.clientId,
402
+ clientSecret: parsed.clientSecret
403
+ },
404
+ ctx
405
+ );
406
+ }
407
+ id = id;
408
+ credentials = wizCredentials;
409
+ accessToken = null;
410
+ accessTokenExpiry = 0;
411
+ tokenEndpoint() {
412
+ return this.settings.tokenEndpoint ?? DEFAULT_TOKEN_ENDPOINT;
413
+ }
414
+ audience() {
415
+ return this.settings.audience ?? DEFAULT_AUDIENCE;
416
+ }
417
+ resourceAllowed(resource) {
418
+ const enabled = this.settings.resources;
419
+ if (!enabled || enabled.length === 0) {
420
+ return true;
421
+ }
422
+ return enabled.includes(resource);
423
+ }
424
+ async refreshAccessToken(signal) {
425
+ const form = new URLSearchParams({
426
+ grant_type: "client_credentials",
427
+ audience: this.audience(),
428
+ client_id: this.creds.clientId,
429
+ client_secret: this.creds.clientSecret
430
+ });
431
+ const res = await this.post(this.tokenEndpoint(), {
432
+ resource: "oauth_token",
433
+ headers: {
434
+ "Content-Type": "application/x-www-form-urlencoded",
435
+ Accept: "application/json",
436
+ "User-Agent": connectorUserAgent("wiz")
437
+ },
438
+ body: form.toString(),
439
+ signal
440
+ });
441
+ const token = res.body.access_token;
442
+ const expiresIn = res.body.expires_in ?? 3600;
443
+ this.accessToken = token;
444
+ this.accessTokenExpiry = Date.now() + Math.max(0, expiresIn - TOKEN_EXPIRY_GRACE_S) * 1e3;
445
+ return token;
446
+ }
447
+ async getAccessToken(signal) {
448
+ if (!this.accessToken || Date.now() >= this.accessTokenExpiry) {
449
+ return this.refreshAccessToken(signal);
450
+ }
451
+ return this.accessToken;
452
+ }
453
+ async graphql(query, variables, resource, signal, retried = false) {
454
+ const token = await this.getAccessToken(signal);
455
+ const res = await this.post(this.settings.apiEndpoint, {
456
+ resource,
457
+ headers: {
458
+ Authorization: `Bearer ${token}`,
459
+ "Content-Type": "application/json",
460
+ Accept: "application/json",
461
+ "User-Agent": connectorUserAgent("wiz")
462
+ },
463
+ body: JSON.stringify({ query, variables }),
464
+ signal
465
+ });
466
+ if (res.status === 401 && !retried) {
467
+ this.accessToken = null;
468
+ this.accessTokenExpiry = 0;
469
+ return this.graphql(query, variables, resource, signal, true);
470
+ }
471
+ const body = res.body;
472
+ if (body.errors && body.errors.length > 0) {
473
+ const messages = body.errors.map((e) => e.message).join("; ");
474
+ throw new Error(`Wiz GraphQL error: ${messages}`);
475
+ }
476
+ if (!body.data) {
477
+ throw new Error(
478
+ `Wiz GraphQL response missing data for resource '${resource}'`
479
+ );
480
+ }
481
+ return res;
482
+ }
483
+ issuesFilter(options) {
484
+ if (!options.since) {
485
+ return void 0;
486
+ }
487
+ return { updatedAt: { after: options.since } };
488
+ }
489
+ vulnerabilitiesFilter(options) {
490
+ if (!options.since) {
491
+ return void 0;
492
+ }
493
+ return { lastDetectedAt: { after: options.since } };
494
+ }
495
+ isPageAllOlderThan(items, sinceMs) {
496
+ if (sinceMs === null || items.length === 0) {
497
+ return false;
498
+ }
499
+ for (const i of items) {
500
+ const ts = parseIsoMs(i.updatedAt ?? i.createdAt);
501
+ if (ts !== null && ts >= sinceMs) {
502
+ return false;
503
+ }
504
+ }
505
+ return true;
506
+ }
507
+ async fetchIssuesPage(page, options, sinceMs, signal) {
508
+ const res = await this.graphql(
509
+ ISSUES_QUERY,
510
+ {
511
+ first: PAGE_SIZE,
512
+ after: page,
513
+ filterBy: this.issuesFilter(options),
514
+ orderBy: { field: "UPDATED_AT", direction: "DESC" }
515
+ },
516
+ "issues",
517
+ signal
518
+ );
519
+ const conn = res.body.data?.issues;
520
+ if (!conn?.nodes || !conn.pageInfo) {
521
+ throw new Error("Wiz GraphQL response missing 'issues' connection");
522
+ }
523
+ const nodes = conn.nodes;
524
+ const next = conn.pageInfo.hasNextPage ? conn.pageInfo.endCursor : null;
525
+ if (this.isPageAllOlderThan(nodes, sinceMs)) {
526
+ return { items: nodes, next: null };
527
+ }
528
+ return { items: nodes, next };
529
+ }
530
+ async fetchVulnerabilitiesPage(page, options, signal) {
531
+ const res = await this.graphql(
532
+ VULNERABILITIES_QUERY,
533
+ {
534
+ first: PAGE_SIZE,
535
+ after: page,
536
+ filterBy: this.vulnerabilitiesFilter(options)
537
+ },
538
+ "vulnerabilities",
539
+ signal
540
+ );
541
+ const conn = res.body.data?.vulnerabilityFindings;
542
+ if (!conn?.nodes || !conn.pageInfo) {
543
+ throw new Error(
544
+ "Wiz GraphQL response missing 'vulnerabilityFindings' connection"
545
+ );
546
+ }
547
+ const nodes = conn.nodes;
548
+ const next = conn.pageInfo.hasNextPage ? conn.pageInfo.endCursor : null;
549
+ return { items: nodes, next };
550
+ }
551
+ async writeIssues(storage, items, sinceMs) {
552
+ const writeEntity = this.resourceAllowed("issues");
553
+ const writeEvent = this.resourceAllowed("issue_events");
554
+ for (const issue of items) {
555
+ const severity = normalizeSeverity(issue.severity);
556
+ const status = normalizeIssueStatus(issue.status);
557
+ const createdMs = parseIsoMs(issue.createdAt);
558
+ const updatedMs = parseIsoMs(issue.updatedAt ?? null) ?? createdMs;
559
+ const resolvedMs = parseIsoMs(issue.resolvedAt ?? null);
560
+ const dueMs = parseIsoMs(issue.dueAt ?? null);
561
+ const snap = issue.entitySnapshot ?? null;
562
+ const cloudProvider = snap?.cloudProvider ?? null;
563
+ const resourceType = snap?.type ?? null;
564
+ const resourceName = snap?.name ?? null;
565
+ const resourceExternalId = snap?.externalId ?? null;
566
+ const ruleId = issue.sourceRule?.id ?? null;
567
+ const ruleName = issue.sourceRule?.name ?? null;
568
+ if (writeEntity) {
569
+ await storage.entity({
570
+ type: ISSUE_ENTITY,
571
+ id: issue.id,
572
+ attributes: {
573
+ severity,
574
+ status,
575
+ issueType: issue.type ?? null,
576
+ resolutionReason: issue.resolutionReason ?? null,
577
+ ruleId,
578
+ ruleName,
579
+ resourceId: snap?.id ?? null,
580
+ resourceName,
581
+ resourceType,
582
+ resourceExternalId,
583
+ cloudProvider,
584
+ createdAt: createdMs,
585
+ resolvedAt: resolvedMs,
586
+ dueAt: dueMs
587
+ },
588
+ updated_at: updatedMs ?? createdMs ?? 0
589
+ });
590
+ }
591
+ if (writeEvent) {
592
+ if (createdMs !== null && (sinceMs === null || createdMs >= sinceMs)) {
593
+ await storage.event({
594
+ name: ISSUE_EVENT,
595
+ start_ts: createdMs,
596
+ end_ts: null,
597
+ attributes: {
598
+ kind: "opened",
599
+ issueId: issue.id,
600
+ severity,
601
+ status,
602
+ cloudProvider,
603
+ resourceType,
604
+ ruleName
605
+ }
606
+ });
607
+ }
608
+ if (resolvedMs !== null && (sinceMs === null || resolvedMs >= sinceMs)) {
609
+ await storage.event({
610
+ name: ISSUE_EVENT,
611
+ start_ts: resolvedMs,
612
+ end_ts: null,
613
+ attributes: {
614
+ kind: "resolved",
615
+ issueId: issue.id,
616
+ severity,
617
+ status,
618
+ cloudProvider,
619
+ resourceType,
620
+ ruleName
621
+ }
622
+ });
623
+ }
624
+ }
625
+ }
626
+ }
627
+ async writeVulnerabilities(storage, items) {
628
+ for (const v of items) {
629
+ const severity = normalizeSeverity(v.severity);
630
+ const status = normalizeVulnStatus(v.status);
631
+ const firstMs = parseIsoMs(v.firstDetectedAt ?? null);
632
+ const lastMs = parseIsoMs(v.lastDetectedAt ?? null) ?? firstMs;
633
+ const resolvedMs = parseIsoMs(v.resolvedAt ?? null);
634
+ await storage.entity({
635
+ type: VULNERABILITY_ENTITY,
636
+ id: v.id,
637
+ attributes: {
638
+ name: v.name ?? null,
639
+ severity,
640
+ status,
641
+ cve: v.vulnerabilityExternalId ?? null,
642
+ assetId: v.vulnerableAsset?.id ?? null,
643
+ assetName: v.vulnerableAsset?.name ?? null,
644
+ assetType: v.vulnerableAsset?.type ?? null,
645
+ cloudPlatform: v.vulnerableAsset?.cloudPlatform ?? null,
646
+ firstDetectedAt: firstMs,
647
+ lastDetectedAt: lastMs,
648
+ resolvedAt: resolvedMs
649
+ },
650
+ updated_at: lastMs ?? firstMs ?? 0
651
+ });
652
+ }
653
+ }
654
+ async clearScopeOnFirstPage(storage, phase, isFull) {
655
+ if (!isFull) {
656
+ return;
657
+ }
658
+ switch (phase) {
659
+ case "issues":
660
+ if (this.resourceAllowed("issues")) {
661
+ await storage.entities([], { types: [ISSUE_ENTITY] });
662
+ }
663
+ if (this.resourceAllowed("issue_events")) {
664
+ await storage.events([], { names: [ISSUE_EVENT] });
665
+ }
666
+ return;
667
+ case "vulnerabilities":
668
+ await storage.entities([], { types: [VULNERABILITY_ENTITY] });
669
+ return;
670
+ }
671
+ }
672
+ resolveCursor(cursor) {
673
+ return isWizSyncCursor(cursor) ? cursor : void 0;
674
+ }
675
+ async sync(options, storage, signal) {
676
+ const cursor = this.resolveCursor(options.cursor);
677
+ const isFull = options.mode === "full";
678
+ const sinceMs = options.since ? Date.parse(options.since) : null;
679
+ const phases = selectActivePhases(
680
+ (r) => r === "vulnerabilities" ? "vulnerabilities" : "issues",
681
+ PHASE_ORDER,
682
+ this.settings.resources
683
+ );
684
+ return paginateChunked({
685
+ phases,
686
+ cursor,
687
+ signal,
688
+ logger: this.logger,
689
+ fetchPage: async (phase, page, sig) => {
690
+ switch (phase) {
691
+ case "issues":
692
+ return this.fetchIssuesPage(page, options, sinceMs, sig);
693
+ case "vulnerabilities":
694
+ return this.fetchVulnerabilitiesPage(page, options, sig);
695
+ }
696
+ },
697
+ writeBatch: async (phase, items, page) => {
698
+ if (page === null) {
699
+ await this.clearScopeOnFirstPage(storage, phase, isFull);
700
+ }
701
+ switch (phase) {
702
+ case "issues":
703
+ await this.writeIssues(storage, items, sinceMs);
704
+ return;
705
+ case "vulnerabilities":
706
+ await this.writeVulnerabilities(
707
+ storage,
708
+ items
709
+ );
710
+ return;
711
+ }
712
+ }
713
+ });
714
+ }
715
+ };
716
+
717
+ // src/index.ts
718
+ var index_default = WizConnector;
719
+ export {
720
+ WizConnector,
721
+ configFields,
722
+ index_default as default,
723
+ doc,
724
+ id,
725
+ wizResources as resources
726
+ };
727
+ //# sourceMappingURL=index.js.map