0

New Issues

by
Published 4 days ago

Emits Wiz issues created since the last run (oldest first). The first run records a watermark and returns nothing.

Scriptยท trigger wiz Verified

The script

Submitted by hugo989 Typescript (fetch-only)
Verified 5 days ago
1
//native
2

3
import * as wmill from "windmill-client"
4

5
/**
6
 * New Issues
7
 * Emits Wiz issues created since the last run. The first run records a watermark and returns nothing; subsequent runs return newly created issues (oldest first).
8
 */
9
export async function main(
10
  auth: RT.Wiz,
11
  severity:
12
    | ("INFORMATIONAL" | "LOW" | "MEDIUM" | "HIGH" | "CRITICAL")[]
13
    | undefined
14
) {
15
  const tokenResponse = await fetch(
16
    auth.auth_url || "https://auth.app.wiz.io/oauth/token",
17
    {
18
      method: "POST",
19
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
20
      body: new URLSearchParams({
21
        grant_type: "client_credentials",
22
        audience: auth.audience || "wiz-api",
23
        client_id: auth.client_id,
24
        client_secret: auth.client_secret,
25
      }),
26
    }
27
  )
28
  if (!tokenResponse.ok) {
29
    throw new Error(`${tokenResponse.status} ${await tokenResponse.text()}`)
30
  }
31
  const { access_token } = (await tokenResponse.json()) as {
32
    access_token: string
33
  }
34

35
  const lastChecked: string | undefined = await wmill.getState()
36

37
  // First run: record the watermark and skip the backlog.
38
  if (!lastChecked) {
39
    await wmill.setState(new Date().toISOString())
40
    return []
41
  }
42

43
  const filterBy: { [key: string]: any } = { createdAt: { after: lastChecked } }
44
  if (severity && severity.length > 0) filterBy.severity = severity
45

46
  const query = `
47
query NewIssues($first: Int, $after: String, $filterBy: IssueFilters) {
48
  issues: issuesV2(first: $first, after: $after, filterBy: $filterBy) {
49
    pageInfo { hasNextPage endCursor }
50
    nodes {
51
      id
52
      type
53
      severity
54
      status
55
      createdAt
56
      sourceRule {
57
        __typename
58
        ... on Control { id name }
59
        ... on CloudConfigurationRule { id name }
60
        ... on CloudEventRule { id name }
61
      }
62
      entitySnapshot { id name type nativeType cloudPlatform region subscriptionName }
63
      projects { id name }
64
    }
65
  }
66
}`
67

68
  const newIssues: any[] = []
69
  let after: string | null = null
70
  // Cap total pages so a large backlog can't run forever.
71
  for (let page = 0; page < 20; page++) {
72
    const response = await fetch(auth.api_endpoint, {
73
      method: "POST",
74
      headers: {
75
        Authorization: `Bearer ${access_token}`,
76
        "Content-Type": "application/json",
77
        Accept: "application/json",
78
      },
79
      body: JSON.stringify({
80
        query,
81
        variables: { first: 100, after, filterBy },
82
      }),
83
    })
84

85
    if (!response.ok) {
86
      throw new Error(`${response.status} ${await response.text()}`)
87
    }
88

89
    const result = (await response.json()) as { data?: any; errors?: any }
90
    if (result.errors) {
91
      throw new Error(JSON.stringify(result.errors))
92
    }
93

94
    const conn = result.data.issues
95
    newIssues.push(...conn.nodes)
96

97
    if (!conn.pageInfo.hasNextPage) break
98
    after = conn.pageInfo.endCursor
99
  }
100

101
  // Order is not guaranteed, so derive the new watermark from the newest
102
  // createdAt seen and sort the output oldest-first ourselves.
103
  if (newIssues.length > 0) {
104
    const newest = newIssues.reduce(
105
      (max, i) => (i.createdAt > max ? i.createdAt : max),
106
      newIssues[0].createdAt
107
    )
108
    await wmill.setState(newest)
109
    newIssues.sort((a, b) => (a.createdAt < b.createdAt ? -1 : 1))
110
  }
111

112
  return newIssues
113
}
114