@convisoappsec/mcp 0.3.1 → 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 +19 -1
- package/package.json +8 -4
- package/src/conviso_mcp/feed_gateway.js +130 -12
- package/src/conviso_mcp/filters.js +54 -0
- package/src/conviso_mcp/graphql_client.js +527 -114
- package/src/conviso_mcp/mutations.js +117 -0
- package/src/conviso_mcp/mutations_catalog.json +3174 -0
- package/src/conviso_mcp/operation_allowlist.js +74 -0
- package/src/conviso_mcp/server.js +933 -21
|
@@ -1,4 +1,117 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
|
+
import * as F from './filters.js';
|
|
3
|
+
import { buildMutationQuery } from './mutations.js';
|
|
4
|
+
|
|
5
|
+
/** Drop only undefined/null keys, keeping false/0/'' so intentional values survive. */
|
|
6
|
+
function compact(obj) {
|
|
7
|
+
const out = {};
|
|
8
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
9
|
+
if (v !== undefined && v !== null) out[k] = v;
|
|
10
|
+
}
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// --- Curated mutation input builders (pure; exported for tests) ---------------
|
|
15
|
+
// Each maps snake_case tool arguments to the GraphQL <Name>Input shape and supports
|
|
16
|
+
// an optional `extra` object spread in for any input field not in the curated signature.
|
|
17
|
+
|
|
18
|
+
export function buildChangeIssueStatusInput(a = {}) {
|
|
19
|
+
return { input: compact({ id: a.issue_id, status: a.status, reason: a.reason, ...(a.extra || {}) }) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function buildSourceCodeVulnerabilityInput(a = {}) {
|
|
23
|
+
return {
|
|
24
|
+
input: compact({
|
|
25
|
+
title: a.title,
|
|
26
|
+
description: a.description,
|
|
27
|
+
solution: a.solution,
|
|
28
|
+
category: a.category,
|
|
29
|
+
patterns: a.patterns,
|
|
30
|
+
reference: a.reference,
|
|
31
|
+
impactLevel: a.impact_level ?? 'MEDIUM',
|
|
32
|
+
probabilityLevel: a.probability_level ?? 'MEDIUM',
|
|
33
|
+
severity: a.severity,
|
|
34
|
+
summary: a.summary ?? '',
|
|
35
|
+
impactDescription: a.impact_description ?? '',
|
|
36
|
+
stepsToReproduce: a.steps_to_reproduce ?? '',
|
|
37
|
+
compromisedEnvironment: a.compromised_environment,
|
|
38
|
+
status: a.status ?? 'DRAFT',
|
|
39
|
+
assetId: a.asset_id,
|
|
40
|
+
projectId: a.project_id,
|
|
41
|
+
codeSnippet: a.code_snippet,
|
|
42
|
+
fileName: a.file_name,
|
|
43
|
+
firstLine: a.first_line,
|
|
44
|
+
vulnerableLine: a.vulnerable_line,
|
|
45
|
+
source: a.source,
|
|
46
|
+
sink: a.sink,
|
|
47
|
+
...(a.extra || {}),
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function buildCreateProjectInput(a = {}) {
|
|
53
|
+
return {
|
|
54
|
+
input: compact({
|
|
55
|
+
companyId: a.company_id,
|
|
56
|
+
typeId: a.type_id,
|
|
57
|
+
label: a.label,
|
|
58
|
+
goal: a.goal,
|
|
59
|
+
scope: a.scope,
|
|
60
|
+
startDate: a.start_date,
|
|
61
|
+
endDate: a.end_date,
|
|
62
|
+
...(a.extra || {}),
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function buildCreateAssetInput(a = {}) {
|
|
68
|
+
return {
|
|
69
|
+
input: compact({
|
|
70
|
+
companyId: a.company_id,
|
|
71
|
+
name: a.name,
|
|
72
|
+
assetType: a.asset_type,
|
|
73
|
+
url: a.url,
|
|
74
|
+
description: a.description,
|
|
75
|
+
businessImpact: a.business_impact,
|
|
76
|
+
exploitability: a.exploitability,
|
|
77
|
+
assetsTagList: a.tags,
|
|
78
|
+
...(a.extra || {}),
|
|
79
|
+
}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function buildCreateTicketInput(a = {}) {
|
|
84
|
+
return {
|
|
85
|
+
input: compact({
|
|
86
|
+
companyId: a.company_id,
|
|
87
|
+
type: a.type,
|
|
88
|
+
title: a.title,
|
|
89
|
+
description: a.description,
|
|
90
|
+
priority: a.priority,
|
|
91
|
+
impact: a.impact,
|
|
92
|
+
...(a.extra || {}),
|
|
93
|
+
}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function buildCreatePentestArtifactInput(a = {}) {
|
|
98
|
+
return {
|
|
99
|
+
input: compact({
|
|
100
|
+
companyId: a.company_id,
|
|
101
|
+
applicationId: a.application_id,
|
|
102
|
+
label: a.label,
|
|
103
|
+
pentestType: a.pentest_type,
|
|
104
|
+
description: a.description,
|
|
105
|
+
scopeText: a.scope_text,
|
|
106
|
+
assigneeEmail: a.assignee_email,
|
|
107
|
+
domains: a.domains,
|
|
108
|
+
inScope: a.in_scope,
|
|
109
|
+
outScope: a.out_scope,
|
|
110
|
+
...(a.extra || {}),
|
|
111
|
+
}),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
2
115
|
|
|
3
116
|
const GraphQLFieldTemplates = {
|
|
4
117
|
complete_issue: `
|
|
@@ -162,6 +275,181 @@ GraphQLFieldTemplates.complete_issue_with_snippet = `
|
|
|
162
275
|
}
|
|
163
276
|
`;
|
|
164
277
|
|
|
278
|
+
export const ISSUES_QUERY = `
|
|
279
|
+
query GetIssues($companyId: ID!, $pagination: PaginationInput!, $filters: IssuesFiltersInput, $sortOptions: [IssueSortOptionInput!]) {
|
|
280
|
+
issues(companyId: $companyId, pagination: $pagination, filters: $filters, sortOptions: $sortOptions) {
|
|
281
|
+
collection {
|
|
282
|
+
id
|
|
283
|
+
title
|
|
284
|
+
severity
|
|
285
|
+
status
|
|
286
|
+
createdAt
|
|
287
|
+
updatedAt
|
|
288
|
+
sla { state dueAt daysRemaining }
|
|
289
|
+
assignedUsers { name email }
|
|
290
|
+
asset { id name }
|
|
291
|
+
project { id label company { id } }
|
|
292
|
+
}
|
|
293
|
+
metadata { totalCount totalPages currentPage limitValue }
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
`;
|
|
297
|
+
|
|
298
|
+
export function buildIssuesVariables(companyId, page = 1, limit = 10, opts = {}) {
|
|
299
|
+
const {
|
|
300
|
+
severities, statuses, slaStates, createdAfter, createdBefore, assigneeEmails,
|
|
301
|
+
search, projectId, assetIds, issueIds, sortBy, order, extraFilters,
|
|
302
|
+
} = opts;
|
|
303
|
+
let built = F.prune({
|
|
304
|
+
severities: F.normalizeEnumList(severities, F.SEVERITIES),
|
|
305
|
+
statuses: F.normalizeEnumList(statuses, F.ISSUE_STATUSES),
|
|
306
|
+
slaStates: F.normalizeEnumList(slaStates, F.SLA_STATES),
|
|
307
|
+
createdAtRange: F.buildDateRange(createdAfter, createdBefore),
|
|
308
|
+
assigneeEmails: assigneeEmails || [],
|
|
309
|
+
partialTitle: search,
|
|
310
|
+
projectIds: (projectId !== undefined && projectId !== null && projectId !== 0) ? [projectId] : [],
|
|
311
|
+
assetIds: assetIds || [],
|
|
312
|
+
ids: issueIds || [],
|
|
313
|
+
});
|
|
314
|
+
if (extraFilters) {
|
|
315
|
+
for (const [k, val] of Object.entries(extraFilters)) {
|
|
316
|
+
if (val !== null && val !== undefined) built[k] = val;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
companyId,
|
|
321
|
+
pagination: { page, perPage: limit },
|
|
322
|
+
filters: built,
|
|
323
|
+
sortOptions: F.buildIssueSortOptions(sortBy, order),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export const ASSETS_QUERY = `
|
|
328
|
+
query ListAssets($companyId: ID!, $page: Int, $limit: Int, $search: AssetsSearch) {
|
|
329
|
+
assets(companyId: $companyId, page: $page, limit: $limit, search: $search) {
|
|
330
|
+
collection {
|
|
331
|
+
id
|
|
332
|
+
name
|
|
333
|
+
assetType
|
|
334
|
+
environment
|
|
335
|
+
audience
|
|
336
|
+
createdAt
|
|
337
|
+
updatedAt
|
|
338
|
+
riskScore { current { value } }
|
|
339
|
+
}
|
|
340
|
+
metadata { totalCount totalPages currentPage limitValue }
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
`;
|
|
344
|
+
|
|
345
|
+
export function buildAssetsVariables(companyId, page = 1, limit = 10, opts = {}) {
|
|
346
|
+
const {
|
|
347
|
+
name, search, tags, technology, businessImpact, exploitability, assetType,
|
|
348
|
+
environmentCompromised, coveredByScan, sortBy, order, extraFilters,
|
|
349
|
+
} = opts;
|
|
350
|
+
const s = F.prune({
|
|
351
|
+
name,
|
|
352
|
+
search,
|
|
353
|
+
tags: tags || [],
|
|
354
|
+
technology: technology || [],
|
|
355
|
+
businessImpact: F.normalizeEnumList(businessImpact, F.BUSINESS_IMPACT),
|
|
356
|
+
exploitability: F.normalizeEnumList(exploitability, F.EXPLOITABILITY),
|
|
357
|
+
assetType,
|
|
358
|
+
sortBy: F.normalizeEnum(sortBy, F.ASSET_SORT_BY, false),
|
|
359
|
+
order: F.normalizeEnum(order, F.ORDER),
|
|
360
|
+
});
|
|
361
|
+
if (environmentCompromised !== null && environmentCompromised !== undefined) {
|
|
362
|
+
s.environmentCompromised = environmentCompromised;
|
|
363
|
+
}
|
|
364
|
+
if (coveredByScan !== null && coveredByScan !== undefined) {
|
|
365
|
+
s.coveredByScan = coveredByScan;
|
|
366
|
+
}
|
|
367
|
+
if (extraFilters) {
|
|
368
|
+
for (const [k, val] of Object.entries(extraFilters)) {
|
|
369
|
+
if (val !== null && val !== undefined) s[k] = val;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return { companyId, page, limit, search: s };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function buildTopVulnsVariables(companyId, opts = {}) {
|
|
376
|
+
const {
|
|
377
|
+
severities, statuses, assetIds, assetTags, createdAfter, createdBefore,
|
|
378
|
+
} = opts;
|
|
379
|
+
const fl = F.prune({
|
|
380
|
+
severities: F.normalizeEnumList(severities, F.SEVERITIES),
|
|
381
|
+
statuses: F.normalizeEnumList(statuses, F.ISSUE_STATUSES),
|
|
382
|
+
assetIds: assetIds || [],
|
|
383
|
+
assetTags: assetTags || [],
|
|
384
|
+
createdAtRange: F.buildDateRange(createdAfter, createdBefore),
|
|
385
|
+
});
|
|
386
|
+
const variables = { companyId };
|
|
387
|
+
if (Object.keys(fl).length) variables.filters = fl;
|
|
388
|
+
return variables;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export const COMPANIES_QUERY = `
|
|
392
|
+
query companies($page: Int, $limit: Int, $params: CompanySearch, $order: OrderScopesParams, $orderType: OrderParams){
|
|
393
|
+
companies(page: $page, limit: $limit, params: $params, order: $order, orderType : $orderType) {
|
|
394
|
+
collection {
|
|
395
|
+
id
|
|
396
|
+
label
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
`;
|
|
401
|
+
|
|
402
|
+
const PROJECTS_QUERY = `
|
|
403
|
+
query projects($page: Int, $limit: Int, $params: ProjectSearch, $sortBy: String, $descending: Boolean){
|
|
404
|
+
projects(page: $page, limit: $limit, params: $params, sortBy: $sortBy, descending : $descending) {
|
|
405
|
+
collection {
|
|
406
|
+
id
|
|
407
|
+
label
|
|
408
|
+
status
|
|
409
|
+
createdAt
|
|
410
|
+
startDate
|
|
411
|
+
endDate
|
|
412
|
+
allocatedAnalyst {
|
|
413
|
+
portalUser {
|
|
414
|
+
name
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
projectType {
|
|
418
|
+
label
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
company {
|
|
422
|
+
id
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
metadata {
|
|
426
|
+
totalCount
|
|
427
|
+
totalPages
|
|
428
|
+
currentPage
|
|
429
|
+
limitValue
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
`;
|
|
434
|
+
|
|
435
|
+
export function buildProjectsVariables(companyId, page = 1, limit = 1000, opts = {}) {
|
|
436
|
+
const {
|
|
437
|
+
search, statuses, projectTypes, createdAfter, createdBefore, tags, analystEmails,
|
|
438
|
+
sortBy = 'createdAt', descending = true,
|
|
439
|
+
} = opts;
|
|
440
|
+
const params = F.prune({
|
|
441
|
+
scopeIdEq: companyId,
|
|
442
|
+
labelCont: search,
|
|
443
|
+
projectStatusLabelIn: statuses || [],
|
|
444
|
+
projectTypeLabelIn: projectTypes || [],
|
|
445
|
+
createdAtGteq: createdAfter,
|
|
446
|
+
createdAtLteq: createdBefore,
|
|
447
|
+
tags: tags || [],
|
|
448
|
+
analystsEmailIn: analystEmails || [],
|
|
449
|
+
});
|
|
450
|
+
return { page, limit, params, sortBy, descending };
|
|
451
|
+
}
|
|
452
|
+
|
|
165
453
|
class GraphQLClient {
|
|
166
454
|
constructor(endpoint, apiKey) {
|
|
167
455
|
this.endpoint = endpoint;
|
|
@@ -203,53 +491,33 @@ class GraphQLClient {
|
|
|
203
491
|
}
|
|
204
492
|
|
|
205
493
|
if (response.data?.errors) {
|
|
494
|
+
const messages = response.data.errors.map((x) => x?.message).filter(Boolean);
|
|
206
495
|
const e = new Error('GraphQL error');
|
|
207
496
|
e.status = 400;
|
|
497
|
+
// Validation/field errors about the caller's own request — safe and useful to surface
|
|
498
|
+
// so the model can correct the input (e.g. a missing required mutation field).
|
|
499
|
+
e.graphqlErrors = messages;
|
|
208
500
|
throw e;
|
|
209
501
|
}
|
|
210
502
|
|
|
211
503
|
return response.data.data;
|
|
212
504
|
}
|
|
213
505
|
|
|
214
|
-
async
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
collection {
|
|
219
|
-
id
|
|
220
|
-
title
|
|
221
|
-
severity
|
|
222
|
-
project {
|
|
223
|
-
company {
|
|
224
|
-
id
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
asset {
|
|
229
|
-
id
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
`;
|
|
235
|
-
|
|
236
|
-
const variables = {
|
|
237
|
-
companyId: company_id,
|
|
238
|
-
filters: { title: search },
|
|
239
|
-
pagination: { page: page, perPage: limit }
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
if (project_id !== null && project_id !== 0) {
|
|
243
|
-
variables.filters.projectIds = [project_id];
|
|
244
|
-
}
|
|
245
|
-
if (Array.isArray(issue_ids) && issue_ids.length > 0) {
|
|
246
|
-
variables.filters.ids = issue_ids;
|
|
247
|
-
}
|
|
248
|
-
if (Array.isArray(asset_ids) && asset_ids.length > 0) {
|
|
249
|
-
variables.filters.assetIds = asset_ids;
|
|
250
|
-
}
|
|
506
|
+
async getIssues(companyId, opts = {}) {
|
|
507
|
+
const variables = buildIssuesVariables(companyId, opts.page || 1, opts.limit || 10, opts);
|
|
508
|
+
return this.execute(ISSUES_QUERY, variables);
|
|
509
|
+
}
|
|
251
510
|
|
|
252
|
-
|
|
511
|
+
// Backward-compatible positional-argument wrapper used by existing FeedGateway callers.
|
|
512
|
+
async get_issues(company_id, search, page = 1, limit = 1, project_id = null, issue_ids = [], asset_ids = []) {
|
|
513
|
+
return this.getIssues(company_id, {
|
|
514
|
+
page,
|
|
515
|
+
limit,
|
|
516
|
+
search,
|
|
517
|
+
projectId: project_id,
|
|
518
|
+
issueIds: issue_ids,
|
|
519
|
+
assetIds: asset_ids,
|
|
520
|
+
});
|
|
253
521
|
}
|
|
254
522
|
|
|
255
523
|
async get_issue_by_id(issue_id, return_snippets = false) {
|
|
@@ -265,56 +533,15 @@ class GraphQLClient {
|
|
|
265
533
|
return this.execute(query, variables);
|
|
266
534
|
}
|
|
267
535
|
|
|
268
|
-
async get_companies(page = 1, limit = 10, search = '') {
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
collection {
|
|
273
|
-
id
|
|
274
|
-
label
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
`;
|
|
279
|
-
const variables = { page, limit, params: { labelCont: search } };
|
|
280
|
-
return this.execute(query, variables);
|
|
536
|
+
async get_companies(page = 1, limit = 10, search = '', label_eq = null) {
|
|
537
|
+
const params = F.prune({ labelCont: search, labelEq: label_eq });
|
|
538
|
+
const variables = { page, limit, params };
|
|
539
|
+
return this.execute(COMPANIES_QUERY, variables);
|
|
281
540
|
}
|
|
282
541
|
|
|
283
|
-
async get_projects(company_id, page = 1, limit = 1000, search = '') {
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
projects(page: $page, limit: $limit, params: $params, sortBy: $sortBy, descending : $descending) {
|
|
287
|
-
collection {
|
|
288
|
-
id
|
|
289
|
-
label
|
|
290
|
-
status
|
|
291
|
-
createdAt
|
|
292
|
-
startDate
|
|
293
|
-
endDate
|
|
294
|
-
allocatedAnalyst {
|
|
295
|
-
portalUser {
|
|
296
|
-
name
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
projectType {
|
|
300
|
-
label
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
company {
|
|
304
|
-
id
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
`;
|
|
310
|
-
const variables = {
|
|
311
|
-
page,
|
|
312
|
-
limit,
|
|
313
|
-
params: { scopeIdEq: company_id, labelCont: search },
|
|
314
|
-
sortBy: 'createdAt',
|
|
315
|
-
descending: true
|
|
316
|
-
};
|
|
317
|
-
return this.execute(query, variables);
|
|
542
|
+
async get_projects(company_id, page = 1, limit = 1000, search = '', opts = {}) {
|
|
543
|
+
const variables = buildProjectsVariables(company_id, page, limit, { search, ...opts });
|
|
544
|
+
return this.execute(PROJECTS_QUERY, variables);
|
|
318
545
|
}
|
|
319
546
|
|
|
320
547
|
async get_project_by_id(project_id) {
|
|
@@ -356,30 +583,9 @@ class GraphQLClient {
|
|
|
356
583
|
return this.execute(query, variables);
|
|
357
584
|
}
|
|
358
585
|
|
|
359
|
-
async get_assets_by_company(company_id, page = 1, limit = 10) {
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
assets(companyId: $companyId, page: $page, limit: $limit) {
|
|
363
|
-
collection {
|
|
364
|
-
id
|
|
365
|
-
name
|
|
366
|
-
assetType
|
|
367
|
-
environment
|
|
368
|
-
audience
|
|
369
|
-
createdAt
|
|
370
|
-
updatedAt
|
|
371
|
-
}
|
|
372
|
-
metadata {
|
|
373
|
-
totalCount
|
|
374
|
-
totalPages
|
|
375
|
-
currentPage
|
|
376
|
-
limitValue
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
`;
|
|
381
|
-
const variables = { companyId: company_id, page, limit };
|
|
382
|
-
return this.execute(query, variables);
|
|
586
|
+
async get_assets_by_company(company_id, page = 1, limit = 10, opts = {}) {
|
|
587
|
+
const variables = buildAssetsVariables(company_id, page, limit, opts);
|
|
588
|
+
return this.execute(ASSETS_QUERY, variables);
|
|
383
589
|
}
|
|
384
590
|
|
|
385
591
|
async get_asset_by_id(asset_id) {
|
|
@@ -412,10 +618,10 @@ class GraphQLClient {
|
|
|
412
618
|
return this.execute(query, variables);
|
|
413
619
|
}
|
|
414
620
|
|
|
415
|
-
async get_top_vulnerabilities(company_id) {
|
|
621
|
+
async get_top_vulnerabilities(company_id, opts = {}) {
|
|
416
622
|
const query = `
|
|
417
|
-
query TopVulnerabilities($companyId: ID
|
|
418
|
-
topVulnerabilities(companyId: $companyId) {
|
|
623
|
+
query TopVulnerabilities($companyId: ID!, $filters: TopVulnerabilitiesFiltersInput) {
|
|
624
|
+
topVulnerabilities(companyId: $companyId, filters: $filters) {
|
|
419
625
|
affectedAssetsCount
|
|
420
626
|
criticalCount
|
|
421
627
|
highCount
|
|
@@ -426,7 +632,7 @@ class GraphQLClient {
|
|
|
426
632
|
}
|
|
427
633
|
}
|
|
428
634
|
`;
|
|
429
|
-
const variables =
|
|
635
|
+
const variables = buildTopVulnsVariables(company_id, opts);
|
|
430
636
|
return this.execute(query, variables);
|
|
431
637
|
}
|
|
432
638
|
|
|
@@ -530,6 +736,213 @@ class GraphQLClient {
|
|
|
530
736
|
const variables = { projectId: project_id, reportId: report_id };
|
|
531
737
|
return this.execute(query, variables);
|
|
532
738
|
}
|
|
739
|
+
|
|
740
|
+
// --- Mutations -------------------------------------------------------------
|
|
741
|
+
|
|
742
|
+
// Generic engine: run any catalogued mutation. Builds the GraphQL document from the
|
|
743
|
+
// SDL-derived catalog (whitelist) and executes it. `variables` is typically { input }.
|
|
744
|
+
async executeMutation(name, variables = {}, returnFields = null) {
|
|
745
|
+
const built = buildMutationQuery(name, variables, returnFields);
|
|
746
|
+
return this.execute(built.query, built.variables);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Curated shortcuts for the most common writes (thin wrappers over executeMutation).
|
|
750
|
+
async change_issue_status(a = {}) {
|
|
751
|
+
return this.executeMutation('changeIssueStatus', buildChangeIssueStatusInput(a));
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
async create_source_code_vulnerability(a = {}) {
|
|
755
|
+
return this.executeMutation('createSourceCodeVulnerability', buildSourceCodeVulnerabilityInput(a));
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
async create_project(a = {}) {
|
|
759
|
+
return this.executeMutation('createProject', buildCreateProjectInput(a));
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
async create_asset(a = {}) {
|
|
763
|
+
return this.executeMutation('createAsset', buildCreateAssetInput(a));
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
async create_ticket(a = {}) {
|
|
767
|
+
return this.executeMutation('createTicket', buildCreateTicketInput(a));
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// --- Curated DAST / AI-Pentest shortcuts ----------------------------------
|
|
771
|
+
async run_dast(a = {}) {
|
|
772
|
+
return this.executeMutation('startConvisoDast', { input: compact({ assetId: a.asset_id, ...(a.extra || {}) }) });
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
async trigger_pentest(a = {}) {
|
|
776
|
+
return this.executeMutation('createPentestExecution', { input: compact({ artifactId: a.artifact_id, ...(a.extra || {}) }) });
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async create_pentest_artifact(a = {}) {
|
|
780
|
+
return this.executeMutation('createPentestArtifact', buildCreatePentestArtifactInput(a));
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// --- Read queries (curated) ------------------------------------------------
|
|
784
|
+
async get_tickets(company_id, { page = 1, limit = 25, search, sort_by, descending, params } = {}) {
|
|
785
|
+
const query = `
|
|
786
|
+
query GetTickets($companyId: ID!, $page: Int, $limit: Int, $sortBy: String, $descending: Boolean, $params: TicketSearch) {
|
|
787
|
+
tickets(companyId: $companyId, page: $page, limit: $limit, sortBy: $sortBy, descending: $descending, params: $params) {
|
|
788
|
+
collection { id title type status priority impact createdAt updatedAt createdBy { name email } assignee { name email } }
|
|
789
|
+
metadata { totalCount totalPages currentPage limitValue }
|
|
790
|
+
}
|
|
791
|
+
}`;
|
|
792
|
+
const p = compact({ search, ...(params || {}) });
|
|
793
|
+
return this.execute(query, { companyId: company_id, page, limit, sortBy: sort_by, descending, params: Object.keys(p).length ? p : undefined });
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
async get_ticket(company_id, ticket_id) {
|
|
797
|
+
const query = `
|
|
798
|
+
query GetTicket($companyId: ID!, $id: ID!) {
|
|
799
|
+
ticket(companyId: $companyId, id: $id) {
|
|
800
|
+
id title description type status priority impact createdAt updatedAt
|
|
801
|
+
createdBy { name email } assignee { name email }
|
|
802
|
+
}
|
|
803
|
+
}`;
|
|
804
|
+
return this.execute(query, { companyId: company_id, id: ticket_id });
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
async get_requirements(scope_id, { page = 1, limit = 25, filters } = {}) {
|
|
808
|
+
const query = `
|
|
809
|
+
query GetRequirements($scopeId: Int!, $pagination: BasePaginationInput!, $filters: RequirementsFilterInput) {
|
|
810
|
+
requirements(scopeId: $scopeId, pagination: $pagination, filters: $filters) {
|
|
811
|
+
collection { id label description global createdAt updatedAt }
|
|
812
|
+
metadata { totalCount totalPages currentPage limitValue }
|
|
813
|
+
}
|
|
814
|
+
}`;
|
|
815
|
+
return this.execute(query, { scopeId: scope_id, pagination: { page, perPage: limit }, filters });
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
async get_requirement(company_id, requirement_id) {
|
|
819
|
+
const query = `
|
|
820
|
+
query GetRequirement($companyId: ID!, $id: ID!) {
|
|
821
|
+
requirement(companyId: $companyId, id: $id) {
|
|
822
|
+
id label description global createdAt updatedAt
|
|
823
|
+
}
|
|
824
|
+
}`;
|
|
825
|
+
return this.execute(query, { companyId: company_id, id: requirement_id });
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
async get_project_requirements(project_id) {
|
|
829
|
+
const query = `
|
|
830
|
+
query GetProjectRequirements($projectId: ID!) {
|
|
831
|
+
projectRequirements(projectId: $projectId) {
|
|
832
|
+
id label description createdAt updatedAt
|
|
833
|
+
}
|
|
834
|
+
}`;
|
|
835
|
+
return this.execute(query, { projectId: project_id });
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
async get_applications(company_id, search = null) {
|
|
839
|
+
const query = `
|
|
840
|
+
query GetApplications($companyId: ID!, $search: String) {
|
|
841
|
+
applications(companyId: $companyId, search: $search) {
|
|
842
|
+
id name description url riskScore assetsCount createdAt updatedAt
|
|
843
|
+
}
|
|
844
|
+
}`;
|
|
845
|
+
return this.execute(query, { companyId: company_id, search });
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
async get_application(company_id, application_id) {
|
|
849
|
+
const query = `
|
|
850
|
+
query GetApplication($companyId: ID!, $id: ID!) {
|
|
851
|
+
application(id: $id, companyId: $companyId) {
|
|
852
|
+
id name description url riskScore assetsCount createdAt updatedAt
|
|
853
|
+
assets { id name }
|
|
854
|
+
}
|
|
855
|
+
}`;
|
|
856
|
+
return this.execute(query, { companyId: company_id, id: application_id });
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
async get_scan_histories(company_id, { assetIds, page = 1, limit = 25, filters, sortOptions } = {}) {
|
|
860
|
+
const query = `
|
|
861
|
+
query GetScanHistories($companyId: ID!, $assetIds: [ID!], $pagination: PaginationInput!, $filters: ScansHistoriesFiltersInput, $sortOptions: [ScansHistoriesSortOptionInput!]) {
|
|
862
|
+
scanHistories(companyId: $companyId, assetIds: $assetIds, pagination: $pagination, filters: $filters, sortOptions: $sortOptions) {
|
|
863
|
+
collection { id status integration createdAt durationInSeconds createdVulnerabilityCount closedVulnerabilityCount importedVulnerabilityCount failureReason asset { id name } }
|
|
864
|
+
metadata { totalCount totalPages currentPage limitValue }
|
|
865
|
+
}
|
|
866
|
+
}`;
|
|
867
|
+
return this.execute(query, { companyId: company_id, assetIds, pagination: { page, perPage: limit }, filters, sortOptions });
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
async get_asset_scans_count(company_id) {
|
|
871
|
+
const query = `
|
|
872
|
+
query GetAssetScansCount($companyId: ID!) {
|
|
873
|
+
assetScansCount(companyId: $companyId) { assetsWithScans assetsWithoutScans consideredScans }
|
|
874
|
+
}`;
|
|
875
|
+
return this.execute(query, { companyId: company_id });
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
async get_sbom_components(company_id, { page = 1, limit = 25, search } = {}) {
|
|
879
|
+
const query = `
|
|
880
|
+
query GetSbomComponents($companyId: ID!, $page: Int, $limit: Int, $search: SbomComponentSearchInput) {
|
|
881
|
+
sbomComponents(companyId: $companyId, page: $page, limit: $limit, search: $search) {
|
|
882
|
+
collection { id name version technology packageManager license issuesBySeverity asset { id name } createdAt updatedAt }
|
|
883
|
+
metadata { totalCount totalPages currentPage limitValue }
|
|
884
|
+
}
|
|
885
|
+
}`;
|
|
886
|
+
return this.execute(query, { companyId: company_id, page, limit, search });
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
async get_pentest_artifacts(company_id, { page = 1, limit = 25, search, assigneeEmail, pentestType, applicationId } = {}) {
|
|
890
|
+
const query = `
|
|
891
|
+
query GetPentestArtifacts($companyId: ID!, $pagination: BasePaginationInput!, $search: String, $assigneeEmail: String, $pentestType: String, $applicationId: ID) {
|
|
892
|
+
pentestArtifacts(companyId: $companyId, pagination: $pagination, search: $search, assigneeEmail: $assigneeEmail, pentestType: $pentestType, applicationId: $applicationId) {
|
|
893
|
+
collection { id label description pentestType createdAt updatedAt useScheduling scheduledAt executionsCount assignee { name email } application { id name } latestExecution { id status runNumber vulnerabilitiesCount createdAt } }
|
|
894
|
+
metadata { totalCount totalPages currentPage limitValue }
|
|
895
|
+
}
|
|
896
|
+
}`;
|
|
897
|
+
return this.execute(query, { companyId: company_id, pagination: { page, perPage: limit }, search, assigneeEmail, pentestType, applicationId });
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
async get_pentest_artifact(artifact_id) {
|
|
901
|
+
const query = `
|
|
902
|
+
query GetPentestArtifact($id: ID!) {
|
|
903
|
+
pentestArtifact(id: $id) {
|
|
904
|
+
id label description pentestType scopeText inScope outScope domains createdAt updatedAt useScheduling scheduledAt
|
|
905
|
+
assignee { name email } application { id name }
|
|
906
|
+
executions { id status runNumber vulnerabilitiesCount createdAt }
|
|
907
|
+
}
|
|
908
|
+
}`;
|
|
909
|
+
return this.execute(query, { id: artifact_id });
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
async get_pentest_execution(execution_id) {
|
|
913
|
+
const query = `
|
|
914
|
+
query GetPentestExecution($id: ID!) {
|
|
915
|
+
pentestExecution(id: $id) {
|
|
916
|
+
id status kind triggerKind runNumber startedAt finishedAt durationSeconds
|
|
917
|
+
vulnerabilitiesCount severityBreakdown nodeCount retestFixedCount retestTotalCount
|
|
918
|
+
project { id label } pentestArtifact { id label } triggeredBy { name email }
|
|
919
|
+
}
|
|
920
|
+
}`;
|
|
921
|
+
return this.execute(query, { id: execution_id });
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
async get_threat_model_artifacts(company_id, { page = 1, limit = 25, search, assigneeEmail, hasVersion } = {}) {
|
|
925
|
+
const query = `
|
|
926
|
+
query GetThreatModelArtifacts($companyId: ID!, $pagination: BasePaginationInput!, $search: String, $assigneeEmail: String, $hasVersion: Boolean) {
|
|
927
|
+
threatModelArtifacts(companyId: $companyId, pagination: $pagination, search: $search, assigneeEmail: $assigneeEmail, hasVersion: $hasVersion) {
|
|
928
|
+
collection { id label description scopeText createdAt updatedAt assignee { name email } latestVersion { id version createdAt } }
|
|
929
|
+
metadata { totalCount totalPages currentPage limitValue }
|
|
930
|
+
}
|
|
931
|
+
}`;
|
|
932
|
+
return this.execute(query, { companyId: company_id, pagination: { page, perPage: limit }, search, assigneeEmail, hasVersion });
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
async get_threat_model_artifact(artifact_id) {
|
|
936
|
+
const query = `
|
|
937
|
+
query GetThreatModelArtifact($id: ID!) {
|
|
938
|
+
threatModelArtifact(id: $id) {
|
|
939
|
+
id label description scopeText createdAt updatedAt
|
|
940
|
+
assignee { name email }
|
|
941
|
+
versions { id version createdAt diagramType scopeText notes }
|
|
942
|
+
}
|
|
943
|
+
}`;
|
|
944
|
+
return this.execute(query, { id: artifact_id });
|
|
945
|
+
}
|
|
533
946
|
}
|
|
534
947
|
|
|
535
948
|
export { GraphQLClient };
|