@eurekadevsecops/radar 1.9.5 → 1.9.7

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/ewa.sarif ADDED
@@ -0,0 +1,274 @@
1
+ {
2
+ "version": "2.1.0",
3
+ "$schema": "https://json.schemastore.org/sarif-2.1.0.json",
4
+ "runs": [
5
+ {
6
+ "tool": {
7
+ "driver": {
8
+ "name": "gitleaks",
9
+ "semanticVersion": "v8.0.0",
10
+ "informationUri": "https://github.com/gitleaks/gitleaks",
11
+ "properties": {
12
+ "officialName": "gitleaks"
13
+ },
14
+ "rules": [
15
+ {
16
+ "id": "bitbucket-client-id",
17
+ "shortDescription": {
18
+ "text": "Discovered a potential Bitbucket Client ID, risking unauthorized repository access and potential codebase exposure."
19
+ }
20
+ },
21
+ {
22
+ "id": "generic-api-key",
23
+ "shortDescription": {
24
+ "text": "Detected a Generic API Key, potentially exposing access to various services and sensitive operations."
25
+ }
26
+ },
27
+ {
28
+ "id": "gitlab-oauth-app-secret",
29
+ "shortDescription": {
30
+ "text": "Identified a GitLab OIDC Application Secret, risking access to apps using GitLab as authentication provider."
31
+ }
32
+ },
33
+ {
34
+ "id": "private-key",
35
+ "shortDescription": {
36
+ "text": "Identified a Private Key, which may compromise cryptographic security and sensitive data encryption."
37
+ }
38
+ },
39
+ {
40
+ "id": "stripe-access-token",
41
+ "shortDescription": {
42
+ "text": "Found a Stripe Access Token, posing a risk to payment processing services and sensitive financial data."
43
+ }
44
+ }
45
+ ]
46
+ }
47
+ },
48
+ "results": [
49
+ {
50
+ "message": {
51
+ "text": "generic-api-key has detected secret for file apps/backend/.env.local."
52
+ },
53
+ "ruleId": "generic-api-key",
54
+ "locations": [
55
+ {
56
+ "physicalLocation": {
57
+ "artifactLocation": {
58
+ "uri": "apps/backend/.env.local"
59
+ },
60
+ "region": {
61
+ "startLine": 121,
62
+ "startColumn": 2,
63
+ "endLine": 121,
64
+ "endColumn": 62,
65
+ "snippet": {
66
+ "text": "0231e56436d8862a967f583939d1d91e955c2bd3"
67
+ }
68
+ }
69
+ }
70
+ }
71
+ ],
72
+ "properties": {
73
+ "tags": []
74
+ }
75
+ },
76
+ {
77
+ "message": {
78
+ "text": "generic-api-key has detected secret for file apps/backend/.env.local."
79
+ },
80
+ "ruleId": "generic-api-key",
81
+ "locations": [
82
+ {
83
+ "physicalLocation": {
84
+ "artifactLocation": {
85
+ "uri": "apps/backend/.env.local"
86
+ },
87
+ "region": {
88
+ "startLine": 132,
89
+ "startColumn": 2,
90
+ "endLine": 132,
91
+ "endColumn": 57,
92
+ "snippet": {
93
+ "text": "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"
94
+ }
95
+ }
96
+ }
97
+ }
98
+ ],
99
+ "properties": {
100
+ "tags": []
101
+ }
102
+ },
103
+ {
104
+ "message": {
105
+ "text": "generic-api-key has detected secret for file apps/backend/.env.local."
106
+ },
107
+ "ruleId": "generic-api-key",
108
+ "locations": [
109
+ {
110
+ "physicalLocation": {
111
+ "artifactLocation": {
112
+ "uri": "apps/backend/.env.local"
113
+ },
114
+ "region": {
115
+ "startLine": 146,
116
+ "startColumn": 2,
117
+ "endLine": 146,
118
+ "endColumn": 54,
119
+ "snippet": {
120
+ "text": "00e3e61c-50ed-44f2-8901-ba56c166b4e5"
121
+ }
122
+ }
123
+ }
124
+ }
125
+ ],
126
+ "properties": {
127
+ "tags": []
128
+ }
129
+ },
130
+ {
131
+ "message": {
132
+ "text": "generic-api-key has detected secret for file apps/backend/.env.local."
133
+ },
134
+ "ruleId": "generic-api-key",
135
+ "locations": [
136
+ {
137
+ "physicalLocation": {
138
+ "artifactLocation": {
139
+ "uri": "apps/backend/.env.local"
140
+ },
141
+ "region": {
142
+ "startLine": 155,
143
+ "startColumn": 2,
144
+ "endLine": 155,
145
+ "endColumn": 58,
146
+ "snippet": {
147
+ "text": "whsec_X92mgLcj9LACgQCfxlEazUtZ5Qb1MSN6"
148
+ }
149
+ }
150
+ }
151
+ }
152
+ ],
153
+ "properties": {
154
+ "tags": []
155
+ }
156
+ },
157
+ {
158
+ "message": {
159
+ "text": "gitlab-oauth-app-secret has detected secret for file apps/backend/.env.local."
160
+ },
161
+ "ruleId": "gitlab-oauth-app-secret",
162
+ "locations": [
163
+ {
164
+ "physicalLocation": {
165
+ "artifactLocation": {
166
+ "uri": "apps/backend/.env.local"
167
+ },
168
+ "region": {
169
+ "startLine": 126,
170
+ "startColumn": 23,
171
+ "endLine": 126,
172
+ "endColumn": 92,
173
+ "snippet": {
174
+ "text": "gloas-776889e1488d83b207ac8a3e3230b71ee8f91ef6cfd6007aa4f5accb579eacd5"
175
+ }
176
+ }
177
+ }
178
+ }
179
+ ],
180
+ "properties": {
181
+ "tags": []
182
+ }
183
+ },
184
+ {
185
+ "message": {
186
+ "text": "private-key has detected secret for file apps/backend/.env.local."
187
+ },
188
+ "ruleId": "private-key",
189
+ "locations": [
190
+ {
191
+ "physicalLocation": {
192
+ "artifactLocation": {
193
+ "uri": "apps/backend/.env.local"
194
+ },
195
+ "region": {
196
+ "startLine": 81,
197
+ "startColumn": 26,
198
+ "endLine": 107,
199
+ "endColumn": 30,
200
+ "snippet": {
201
+ "text": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAs1jrtHlMuqGnpah5PdGJ1Tzeoth+dWJ4hP1Nr8a50z0JCBHT\n7NZ0XW/DmNroKXqxmVW7U1iKw0g96+5xRuJiFDLs+qeJGXiog7gOETKI3gHum2h2\nj/VcGzMRfe7gH4le+69SFnCK9+9nB5H4oVXQ5JimUA74pmRxKWBY9+96BsMUVBp+\n2PYFtOsNHplx+UlzAQ3KnbDlE73aAZpPGW1GKrw8ZkrN22srDokp2ZbjWqbp54mc\nht0/7g2h+6naJonhNOKmadHOb9T2OcwKgmNrNINQq0R7X4fb7Wcs6Ecru8H+8H2h\nInViwTuI17gdrI8iBeNCNayKigdTu5BTFJurJwIDAQABAoIBAFMx330De81jacJV\nyZAcoGSTbO97oAXR3PhMDHqKo+7SdFsS8gz+WlJxovlIKsP7D2GCvHfoLc5yt463\nOELahwa5rOaFSvrO1tTrT1M47vaVTNs4dS9IcSaI5QdXBQ58CdyUsm6IXF6w5klD\ntGMazOEN0rB2kW+WTkwUTpEMvL1ff8BZEvYygv9a8hgPLfxyu4Lvkan7059UG766\nOW5eLQ8eaf+jjWw3YRsty8YK+w3kGOErIHZ7qw+JiQ6nbSxUKbqAGJCXwsvvLiKs\nNfhxeksHqod4CQMIrFDCwYw5pEW9Ji3AWj1XS8h0THYM6z5XPijHsvRVSLC1Fl45\nWFHC6cECgYEA5mFN+XixFLqXGb0N4jtZVC/WUJs+ER9sasMJrwd7moWAyZvjMdkA\nDKq2TeiuNRGcjP9VWM9EWDqNiyQEBxUcwwT3RD5KRykNkLYz51nJZcRmijQUb7ss\nmtLd+tGTaFDfMU3dRihzwYmfYiKJpqwa8mgeTZEXEEU8lEEh7JfrO8cCgYEAx0rD\niI6Lv/7MwoG+cH7eZR3ezyfiGFAbWOfMKtBxLsVyNSzl3JCnyaWf2tsrJSmYlVUa\nANNo9gXvSfrrFjnPOC9ZLEnB2xl1XoJagBFj5Qwf3Giy/i9eiRSu0SyT6WrSzfKa\nOaIFhwwsx1s6Qoeck4UqQFqUgk+FnN3BuCaeVaECgYB0AsfrOnWhxJxWX7dgFxbS\nqAw6JxLIOJS15mU3+IKru1KxM4jjDy1RM539+Y/QNYAqGGH4CNeXvlSMnqRQlLcZ\nFaUWfm+VCf1ExBu7AqHCV3ZzXep0oULC7DDQHz0lqKPcBiPJMpGoAg96sX2zqrMf\nIoMv+EIu9U6eMXZN1+qi/QKBgB0Mv93a8XIGITDFGs5pH9/bb8wAg0uJ+cKG31Lq\nWWU48MHhjowNJfgVxWxwgCSFoLE723N9XZJnIQ9GnRf7S0JkXHpBMhnO5zXkiG6c\nmlQb5VUKifTVUNFoi2cAOXtPz/SnRWXbQTUDSE+y85YZEHDMe3EwAu/PyakpBgDi\n2DehAoGBAKJtxule5t0JQpFzYtk7Ojxv8ppellrqevoX/z+xNG58AT97/JtQAD0D\nTwz1fuptQFe1Mq+cA561hZaTH9MqhTOCVxdP7tMGnGmIT+MXU24o4EhGN0EFtsvR\npKxf8/C9KgyrbfXXzb/LMQIZko0cAFI47EMo/Ad8wgyPDsTnTcJZ\n-----END RSA PRIVATE KEY-----"
202
+ }
203
+ }
204
+ }
205
+ }
206
+ ],
207
+ "properties": {
208
+ "tags": []
209
+ }
210
+ },
211
+ {
212
+ "message": {
213
+ "text": "bitbucket-client-id has detected secret for file apps/backend/.env.local."
214
+ },
215
+ "ruleId": "bitbucket-client-id",
216
+ "locations": [
217
+ {
218
+ "physicalLocation": {
219
+ "artifactLocation": {
220
+ "uri": "apps/backend/.env.local"
221
+ },
222
+ "region": {
223
+ "startLine": 116,
224
+ "startColumn": 2,
225
+ "endLine": 116,
226
+ "endColumn": 57,
227
+ "snippet": {
228
+ "text": "KbPZjucUXpxhqmKjP6wbtS5BfEERxdnb"
229
+ }
230
+ }
231
+ }
232
+ }
233
+ ],
234
+ "properties": {
235
+ "tags": []
236
+ }
237
+ },
238
+ {
239
+ "message": {
240
+ "text": "stripe-access-token has detected secret for file apps/backend/.env.local."
241
+ },
242
+ "ruleId": "stripe-access-token",
243
+ "locations": [
244
+ {
245
+ "physicalLocation": {
246
+ "artifactLocation": {
247
+ "uri": "apps/backend/.env.local"
248
+ },
249
+ "region": {
250
+ "startLine": 154,
251
+ "startColumn": 24,
252
+ "endLine": 154,
253
+ "endColumn": 130,
254
+ "snippet": {
255
+ "text": "sk_test_51RYvkf2YG6fO9qlhtYIIbnGSXSr6xpzqdqryyPk58EVMgZMjIviKEXde8r55HE4vbVgzKwNb7owr74qRMEHUKakC007aUEcU3n"
256
+ }
257
+ }
258
+ }
259
+ }
260
+ ],
261
+ "properties": {
262
+ "tags": []
263
+ }
264
+ }
265
+ ],
266
+ "properties": {
267
+ "repository": {
268
+ "type": "git",
269
+ "url": "https://github.com/EurekaDevSecOps/app.git"
270
+ }
271
+ }
272
+ }
273
+ ]
274
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eurekadevsecops/radar",
3
- "version": "1.9.5",
3
+ "version": "1.9.7",
4
4
  "description": "Radar is an open-source orchestrator of security scanners.",
5
5
  "homepage": "https://www.eurekadevsecops.com/radar",
6
6
  "keywords": [
@@ -20,6 +20,8 @@
20
20
  "radar": "cli.js"
21
21
  },
22
22
  "scripts": {
23
+ "commit": "npx cz",
24
+ "prepare": "husky || true",
23
25
  "test": "standard"
24
26
  },
25
27
  "repository": {
@@ -37,6 +39,21 @@
37
39
  "tiny-spinner": "^2.0.5"
38
40
  },
39
41
  "devDependencies": {
42
+ "@commitlint/cli": "^20.1.0",
43
+ "@commitlint/config-conventional": "^20.0.0",
44
+ "@commitlint/cz-commitlint": "^20.1.0",
45
+ "commitizen": "^4.3.1",
46
+ "commitlint-plugin-selective-scope": "^1.0.1",
47
+ "conventional-changelog-atom": "^5.0.0",
48
+ "cz-conventional-changelog": "^3.3.0",
49
+ "cz-git": "^1.12.0",
50
+ "husky": "^9.1.7",
40
51
  "standard": "*"
52
+ },
53
+ "config": {
54
+ "commitizen": {
55
+ "path": "cz-git",
56
+ "czConfig": ".config/commitlint/cz.config.mjs"
57
+ }
41
58
  }
42
59
  }
@@ -4,6 +4,8 @@ const path = require('node:path')
4
4
  const os = require('node:os')
5
5
  const SARIF = require('../util/sarif')
6
6
  const runner = require('../util/runner')
7
+ const { DateTime } = require('luxon')
8
+
7
9
  module.exports = {
8
10
  summary: 'scan for vulnerabilities',
9
11
  args: {
@@ -63,12 +65,11 @@ module.exports = {
63
65
 
64
66
  Runs entirely on your machine — by default, Radar CLI doesn’t upload any findings.
65
67
  Your vulnerabilities stay local and private. To upload results to Eureka ASPM,
66
- provide your API credentials via two environment variables: 'EUREKA_AGENT_TOKEN'
67
- (your API token) and 'EUREKA_PROFILE' (your profile ID). When these are set, Radar CLI
68
- automatically uploads results after each scan letting you view your full scan
69
- history and all findings in the Eureka ASPM Dashboard. To prevent Radar CLI from
70
- uploading scan findings even when you have 'EUREKA_AGENT_TOKEN' and 'EUREKA_PROFILE'
71
- set, you can pass the LOCAL option on the command line.
68
+ provide your API credentials through the 'EUREKA_AGENT_TOKEN' environment variable.
69
+ When set, Radar CLI automatically uploads results after each scan letting you view
70
+ your full scan history and all findings in the Eureka ASPM Dashboard. To prevent
71
+ Radar CLI from uploading scan findings even when you have 'EUREKA_AGENT_TOKEN' set,
72
+ you can pass the LOCAL option on the command line.
72
73
 
73
74
  Exit codes:
74
75
  0 - Clean and successful scan. No errors, warnings, or notes.
@@ -159,12 +160,18 @@ module.exports = {
159
160
  log(`INFO: Running a local scan.\n`)
160
161
  }
161
162
 
163
+ // Get target git metadata.
164
+ const metadata = git.metadata(target)
165
+ if (metadata.type === 'error') throw new Error(`${metadata.error.code}: ${metadata.error.details}`)
166
+
162
167
  // Send telemetry: scan started.
163
168
  let scanID = undefined
169
+ const timestamp = DateTime.now().toISO()
170
+
164
171
  if (telemetry.enabled && !args.LOCAL) {
165
172
  // TODO: Should pass scanID to the server; not read it from the server.
166
173
  try {
167
- const res = await telemetry.send(`scans/started`, {}, { scanners: scanners.map((s) => s.name) })
174
+ const res = await telemetry.send(`scans/started`, {}, { scanners: scanners.map((s) => s.name), metadata, timestamp })
168
175
  if (!res.ok) throw new Error(`[${res.status}] ${res.statusText}: ${await res.text()}`)
169
176
  const data = await res.json()
170
177
  scanID = data.scan_id
@@ -181,14 +188,10 @@ module.exports = {
181
188
  }
182
189
  }
183
190
 
184
- // Send telemetry: git metadata.
185
- const metadata = git.metadata(target)
186
- if (metadata.type === 'error') throw new Error(`${metadata.error.code}: ${metadata.error.details}`)
191
+ // Send telemetry: scan started (stage 2).
187
192
  if (telemetry.enabled && scanID && !args.LOCAL) {
188
- let res = await telemetry.send(`scans/:scanID/metadata`, { scanID }, { metadata })
189
- if (!res.ok) log(`WARNING: Scan metadata (stage 1) telemetry upload failed: [${res.status}] ${res.statusText}: ${await res.text()}`)
190
- res = await telemetry.sendSensitive(`scans/:scanID/metadata`, { scanID }, { metadata })
191
- if (!res.ok) log(`WARNING: Scan metadata (stage 2) telemetry upload failed: [${res.status}] ${res.statusText}: ${await res.text()}`)
193
+ const res = await telemetry.sendSensitive(`scans/:scanID/started`, { scanID }, { metadata, timestamp })
194
+ if (!res.ok) log(`WARNING: Scan started (stage 2) telemetry upload failed: [${res.status}] ${res.statusText}: ${await res.text()}`)
192
195
  }
193
196
 
194
197
  // Run scanners.
@@ -234,7 +237,7 @@ module.exports = {
234
237
 
235
238
  // Send telemetry: scan summary.
236
239
  if (telemetry.enabled && scanID && !args.LOCAL) {
237
- const res = await telemetry.send(`scans/:scanID/completed`, { scanID }, summary)
240
+ const res = await telemetry.send(`scans/:scanID/completed`, { scanID }, { summary })
238
241
  if (!res.ok) log(`WARNING: Scan status (completed) telemetry upload failed: [${res.status}] ${res.statusText}: ${await res.text()}`)
239
242
  }
240
243
 
@@ -11,15 +11,21 @@ const telemetry = require('./telemetry')
11
11
 
12
12
  All available telemetry events:
13
13
  ```js
14
- telemetry.send(`scans/:scanID/started`, { scanID }, { scanners })
14
+ telemetry.send(`scans/started`, {}, { scanners, metadata, timestamp })
15
+ telemetry.sendSensitive(`scans/:scanID/started`, { scanID }, { scanners, metadata, timestamp })
15
16
  telemetry.send(`scans/:scanID/completed`, { scanID }, { status, findings, log })
16
17
  telemetry.sendSensitive(`scans/:scanID/log`, { scanID }, log)
17
18
  telemetry.sendSensitive(`scans/:scanID/findings`, { scanID }, sarif)
18
19
  ```
19
20
 
20
- To send a telemetry event indicating that a scan has started:
21
+ To send a telemetry event indicating that a scan has started (stage 1):
21
22
  ```js
22
- telemetry.send(`scans/:scanID/started`, { scanID }, { scanners })
23
+ telemetry.send(`scans/started`, {}, { scanners, metadata })
24
+ ```
25
+
26
+ To send a telemetry event indicating that a scan has started (stage 2):
27
+ ```js
28
+ telemetry.sendSensitive(`scans/:scanID/started`, { scanID }, { metadata })
23
29
  ```
24
30
 
25
31
  To send a telemetry event indicating that a scan has completed:
@@ -9,7 +9,7 @@ class Telemetry {
9
9
 
10
10
  constructor() {
11
11
  this.enabled = !!this.#EUREKA_AGENT_TOKEN
12
- this.#EWA_URL = this.#claims(this.#EUREKA_AGENT_TOKEN)?.aud?.replace(/\/$/, '')
12
+ this.#EWA_URL = this.#claims(this.#EUREKA_AGENT_TOKEN).aud?.replace(/\/$/, '')
13
13
  }
14
14
 
15
15
  async send(path, params, body, token) {
@@ -71,7 +71,8 @@ class Telemetry {
71
71
  'Content-Type': 'application/json',
72
72
  'User-Agent': this.#USER_AGENT,
73
73
  'Accept': 'application/json'
74
- }
74
+ },
75
+ body: JSON.stringify({ profileId: process.env.EUREKA_PROFILE })
75
76
  })
76
77
  if (!response.ok) throw new Error(`Internal Error: Failed to get VDBE auth token from EWA: ${response.statusText}: ${await response.text()}`)
77
78
  const data = await response.json()
@@ -82,9 +83,9 @@ class Telemetry {
82
83
  const claims = this.#claims(token ?? this.#EUREKA_AGENT_TOKEN)
83
84
  const aud = claims.aud.replace(/\/$/, '')
84
85
  if (path === `scans/started`) return `${aud}/scans/started`
86
+ if (path === `scans/:scanID/started`) return `${aud}/scans/${params.scanID}/started`
85
87
  if (path === `scans/:scanID/completed`) return `${aud}/scans/${params.scanID}/completed`
86
88
  if (path === `scans/:scanID/failed`) return `${aud}/scans/${params.scanID}/completed`
87
- if (path === `scans/:scanID/metadata`) return `${aud}/scans/${params.scanID}/metadata`
88
89
  if (path === `scans/:scanID/results`) return `${aud}/scans/${params.scanID}/results`
89
90
  throw new Error(`Internal Error: Unknown telemetry event: POST ${path}`)
90
91
  }
@@ -92,7 +93,11 @@ class Telemetry {
92
93
  #toReceiveURL(path, params, token) {
93
94
  const claims = this.#claims(token ?? this.#EUREKA_AGENT_TOKEN)
94
95
  const aud = claims.aud.replace(/\/$/, '')
95
- if (path === `scans/:scanID/summary`) return `${aud}/scans/${params.scanID}/summary?profileId=${process.env.EUREKA_PROFILE}`
96
+ if (path === `scans/:scanID/summary`) {
97
+ const profileId = process.env.EUREKA_PROFILE
98
+ const base = `${aud}/scans/${params.scanID}/summary`
99
+ return profileId ? `${base}?profileId=${profileId}` : base
100
+ }
96
101
  throw new Error(`Internal Error: Unknown telemetry event: GET ${path}`)
97
102
  }
98
103
 
@@ -102,11 +107,11 @@ class Telemetry {
102
107
  }
103
108
 
104
109
  #toBody(path, body) {
105
- if (path === `scans/started`) body = { ...body, timestamp: DateTime.now().toISO(), profile_id: process.env.EUREKA_PROFILE }
106
- if (path === `scans/:scanID/completed`) body = { ...this.#toFindings(body), timestamp: DateTime.now().toISO(), status: 'success', log: { sizeBytes: 0, warnings: 0, errors: 0, link: 'none' }, params: { id: '' }}
110
+ if (path === `scans/started`) body = { ...body, profileId: process.env.EUREKA_PROFILE }
111
+ if (path === `scans/:scanID/started`) body = { ...body, profileId: process.env.EUREKA_PROFILE }
112
+ if (path === `scans/:scanID/completed`) body = { ...this.#toFindings(body.summary), timestamp: DateTime.now().toISO(), status: 'success', log: { sizeBytes: 0, warnings: 0, errors: 0, link: 'none' }, profileId: process.env.EUREKA_PROFILE, params: { id: '' }}
107
113
  if (path === `scans/:scanID/failed`) body = { ...body, timestamp: DateTime.now().toISO(), status: 'failure', findings: { total: 0, critical: 0, high: 0, med: 0, low: 0 }, log: { sizeBytes: 0, warnings: 0, errors: 0, link: 'none' }, params: { id: '' }}
108
- if (path === `scans/:scanID/metadata`) body = { metadata: body.metadata, profileId: process.env.EUREKA_PROFILE }
109
- if (path === `scans/:scanID/results`) body = { findings: body.findings /* SARIF */, profileId: process.env.EUREKA_PROFILE, log: Buffer.from(body.log, 'utf8').toString('base64') }
114
+ if (path === `scans/:scanID/results`) body = { findings: body.findings /* SARIF */, log: Buffer.from(body.log, 'utf8').toString('base64'), profileId: process.env.EUREKA_PROFILE }
110
115
  return JSON.stringify(body)
111
116
  }
112
117
 
@@ -121,7 +126,6 @@ class Telemetry {
121
126
  }
122
127
  }
123
128
  }
124
-
125
129
  }
126
130
 
127
131
  module.exports = {