@convisoappsec/mcp 0.3.1 → 0.6.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.
@@ -1,4 +1,118 @@
1
1
  import axios from 'axios';
2
+ import https from 'node:https';
3
+ import * as F from './filters.js';
4
+ import { buildMutationQuery } from './mutations.js';
5
+
6
+ /** Drop only undefined/null keys, keeping false/0/'' so intentional values survive. */
7
+ function compact(obj) {
8
+ const out = {};
9
+ for (const [k, v] of Object.entries(obj)) {
10
+ if (v !== undefined && v !== null) out[k] = v;
11
+ }
12
+ return out;
13
+ }
14
+
15
+ // --- Curated mutation input builders (pure; exported for tests) ---------------
16
+ // Each maps snake_case tool arguments to the GraphQL <Name>Input shape and supports
17
+ // an optional `extra` object spread in for any input field not in the curated signature.
18
+
19
+ export function buildChangeIssueStatusInput(a = {}) {
20
+ return { input: compact({ id: a.issue_id, status: a.status, reason: a.reason, ...(a.extra || {}) }) };
21
+ }
22
+
23
+ export function buildSourceCodeVulnerabilityInput(a = {}) {
24
+ return {
25
+ input: compact({
26
+ title: a.title,
27
+ description: a.description,
28
+ solution: a.solution,
29
+ category: a.category,
30
+ patterns: a.patterns,
31
+ reference: a.reference,
32
+ impactLevel: a.impact_level ?? 'MEDIUM',
33
+ probabilityLevel: a.probability_level ?? 'MEDIUM',
34
+ severity: a.severity,
35
+ summary: a.summary ?? '',
36
+ impactDescription: a.impact_description ?? '',
37
+ stepsToReproduce: a.steps_to_reproduce ?? '',
38
+ compromisedEnvironment: a.compromised_environment,
39
+ status: a.status ?? 'DRAFT',
40
+ assetId: a.asset_id,
41
+ projectId: a.project_id,
42
+ codeSnippet: a.code_snippet,
43
+ fileName: a.file_name,
44
+ firstLine: a.first_line,
45
+ vulnerableLine: a.vulnerable_line,
46
+ source: a.source,
47
+ sink: a.sink,
48
+ ...(a.extra || {}),
49
+ }),
50
+ };
51
+ }
52
+
53
+ export function buildCreateProjectInput(a = {}) {
54
+ return {
55
+ input: compact({
56
+ companyId: a.company_id,
57
+ typeId: a.type_id,
58
+ label: a.label,
59
+ goal: a.goal,
60
+ scope: a.scope,
61
+ startDate: a.start_date,
62
+ endDate: a.end_date,
63
+ ...(a.extra || {}),
64
+ }),
65
+ };
66
+ }
67
+
68
+ export function buildCreateAssetInput(a = {}) {
69
+ return {
70
+ input: compact({
71
+ companyId: a.company_id,
72
+ name: a.name,
73
+ assetType: a.asset_type,
74
+ url: a.url,
75
+ description: a.description,
76
+ businessImpact: a.business_impact,
77
+ exploitability: a.exploitability,
78
+ assetsTagList: a.tags,
79
+ ...(a.extra || {}),
80
+ }),
81
+ };
82
+ }
83
+
84
+ export function buildCreateTicketInput(a = {}) {
85
+ return {
86
+ input: compact({
87
+ companyId: a.company_id,
88
+ type: a.type,
89
+ title: a.title,
90
+ description: a.description,
91
+ priority: a.priority,
92
+ impact: a.impact,
93
+ ...(a.extra || {}),
94
+ }),
95
+ };
96
+ }
97
+
98
+ export function buildCreatePentestArtifactInput(a = {}) {
99
+ return {
100
+ input: compact({
101
+ companyId: a.company_id,
102
+ applicationId: a.application_id,
103
+ label: a.label,
104
+ pentestType: a.pentest_type,
105
+ description: a.description,
106
+ scopeText: a.scope_text,
107
+ assigneeEmail: a.assignee_email,
108
+ domains: a.domains,
109
+ inScope: a.in_scope,
110
+ outScope: a.out_scope,
111
+ ...(a.extra || {}),
112
+ }),
113
+ };
114
+ }
115
+
2
116
 
3
117
  const GraphQLFieldTemplates = {
4
118
  complete_issue: `
@@ -162,6 +276,191 @@ GraphQLFieldTemplates.complete_issue_with_snippet = `
162
276
  }
163
277
  `;
164
278
 
279
+ export const ISSUES_QUERY = `
280
+ query GetIssues($companyId: ID!, $pagination: PaginationInput!, $filters: IssuesFiltersInput, $sortOptions: [IssueSortOptionInput!]) {
281
+ issues(companyId: $companyId, pagination: $pagination, filters: $filters, sortOptions: $sortOptions) {
282
+ collection {
283
+ id
284
+ title
285
+ severity
286
+ status
287
+ createdAt
288
+ updatedAt
289
+ sla { state dueAt daysRemaining }
290
+ assignedUsers { name email }
291
+ asset { id name }
292
+ project { id label company { id } }
293
+ }
294
+ metadata { totalCount totalPages currentPage limitValue }
295
+ }
296
+ }
297
+ `;
298
+
299
+ export function buildIssuesVariables(companyId, page = 1, limit = 10, opts = {}) {
300
+ const {
301
+ severities, statuses, slaStates, createdAfter, createdBefore, assigneeEmails,
302
+ search, projectId, assetIds, issueIds, sortBy, order, extraFilters,
303
+ } = opts;
304
+ let built = F.prune({
305
+ severities: F.normalizeEnumList(severities, F.SEVERITIES),
306
+ statuses: F.normalizeEnumList(statuses, F.ISSUE_STATUSES),
307
+ slaStates: F.normalizeEnumList(slaStates, F.SLA_STATES),
308
+ createdAtRange: F.buildDateRange(createdAfter, createdBefore),
309
+ assigneeEmails: assigneeEmails || [],
310
+ partialTitle: search,
311
+ projectIds: (projectId !== undefined && projectId !== null && projectId !== 0) ? [projectId] : [],
312
+ assetIds: assetIds || [],
313
+ ids: issueIds || [],
314
+ });
315
+ if (extraFilters) {
316
+ for (const [k, val] of Object.entries(extraFilters)) {
317
+ if (val !== null && val !== undefined) built[k] = val;
318
+ }
319
+ }
320
+ return {
321
+ companyId,
322
+ pagination: { page, perPage: limit },
323
+ filters: built,
324
+ sortOptions: F.buildIssueSortOptions(sortBy, order),
325
+ };
326
+ }
327
+
328
+ export const ASSETS_QUERY = `
329
+ query ListAssets($companyId: ID!, $page: Int, $limit: Int, $search: AssetsSearch) {
330
+ assets(companyId: $companyId, page: $page, limit: $limit, search: $search) {
331
+ collection {
332
+ id
333
+ name
334
+ assetType
335
+ environment
336
+ audience
337
+ createdAt
338
+ updatedAt
339
+ riskScore { current { value } }
340
+ }
341
+ metadata { totalCount totalPages currentPage limitValue }
342
+ }
343
+ }
344
+ `;
345
+
346
+ export function buildAssetsVariables(companyId, page = 1, limit = 10, opts = {}) {
347
+ const {
348
+ name, search, tags, technology, businessImpact, exploitability, assetType,
349
+ environmentCompromised, coveredByScan, sortBy, order, extraFilters,
350
+ } = opts;
351
+ const s = F.prune({
352
+ name,
353
+ search,
354
+ tags: tags || [],
355
+ technology: technology || [],
356
+ businessImpact: F.normalizeEnumList(businessImpact, F.BUSINESS_IMPACT),
357
+ exploitability: F.normalizeEnumList(exploitability, F.EXPLOITABILITY),
358
+ assetType,
359
+ sortBy: F.normalizeEnum(sortBy, F.ASSET_SORT_BY, false),
360
+ order: F.normalizeEnum(order, F.ORDER),
361
+ });
362
+ if (environmentCompromised !== null && environmentCompromised !== undefined) {
363
+ s.environmentCompromised = environmentCompromised;
364
+ }
365
+ if (coveredByScan !== null && coveredByScan !== undefined) {
366
+ s.coveredByScan = coveredByScan;
367
+ }
368
+ if (extraFilters) {
369
+ for (const [k, val] of Object.entries(extraFilters)) {
370
+ if (val !== null && val !== undefined) s[k] = val;
371
+ }
372
+ }
373
+ return { companyId, page, limit, search: s };
374
+ }
375
+
376
+ export function buildTopVulnsVariables(companyId, opts = {}) {
377
+ const {
378
+ severities, statuses, assetIds, assetTags, createdAfter, createdBefore,
379
+ } = opts;
380
+ const fl = F.prune({
381
+ severities: F.normalizeEnumList(severities, F.SEVERITIES),
382
+ statuses: F.normalizeEnumList(statuses, F.ISSUE_STATUSES),
383
+ assetIds: assetIds || [],
384
+ assetTags: assetTags || [],
385
+ createdAtRange: F.buildDateRange(createdAfter, createdBefore),
386
+ });
387
+ const variables = { companyId };
388
+ if (Object.keys(fl).length) variables.filters = fl;
389
+ return variables;
390
+ }
391
+
392
+ export const COMPANIES_QUERY = `
393
+ query companies($page: Int, $limit: Int, $params: CompanySearch, $order: OrderScopesParams, $orderType: OrderParams){
394
+ companies(page: $page, limit: $limit, params: $params, order: $order, orderType : $orderType) {
395
+ collection {
396
+ id
397
+ label
398
+ }
399
+ }
400
+ }
401
+ `;
402
+
403
+ const PROJECTS_QUERY = `
404
+ query projects($page: Int, $limit: Int, $params: ProjectSearch, $sortBy: String, $descending: Boolean){
405
+ projects(page: $page, limit: $limit, params: $params, sortBy: $sortBy, descending : $descending) {
406
+ collection {
407
+ id
408
+ label
409
+ status
410
+ createdAt
411
+ startDate
412
+ endDate
413
+ allocatedAnalyst {
414
+ portalUser {
415
+ name
416
+ }
417
+ }
418
+ projectType {
419
+ label
420
+ }
421
+
422
+ company {
423
+ id
424
+ }
425
+ }
426
+ metadata {
427
+ totalCount
428
+ totalPages
429
+ currentPage
430
+ limitValue
431
+ }
432
+ }
433
+ }
434
+ `;
435
+
436
+ export function buildProjectsVariables(companyId, page = 1, limit = 1000, opts = {}) {
437
+ const {
438
+ search, statuses, projectTypes, createdAfter, createdBefore, tags, analystEmails,
439
+ sortBy = 'createdAt', descending = true,
440
+ } = opts;
441
+ const params = F.prune({
442
+ scopeIdEq: companyId,
443
+ labelCont: search,
444
+ projectStatusLabelIn: statuses || [],
445
+ projectTypeLabelIn: projectTypes || [],
446
+ createdAtGteq: createdAfter,
447
+ createdAtLteq: createdBefore,
448
+ tags: tags || [],
449
+ analystsEmailIn: analystEmails || [],
450
+ });
451
+ return { page, limit, params, sortBy, descending };
452
+ }
453
+
454
+ // Shared HTTP client: keep-alive reuses the TLS connection across the many sequential
455
+ // calls an agent session makes; the timeout stops a hung upstream from hanging a tool
456
+ // call (and the MCP client) forever.
457
+ const httpClient = axios.create({
458
+ timeout: 30_000,
459
+ httpsAgent: new https.Agent({ keepAlive: true }),
460
+ });
461
+
462
+ const RETRYABLE_STATUS = new Set([429, 502, 503]);
463
+
165
464
  class GraphQLClient {
166
465
  constructor(endpoint, apiKey) {
167
466
  this.endpoint = endpoint;
@@ -174,16 +473,30 @@ class GraphQLClient {
174
473
  }
175
474
 
176
475
  async execute(query, variables = {}) {
177
- const payload = { query, variables };
476
+ // Queries are idempotent retry a transient failure once. Mutations never retry.
477
+ const isRead = /^\s*query\b/i.test(query);
478
+ try {
479
+ return await this.#post(query, variables);
480
+ } catch (err) {
481
+ const transient = RETRYABLE_STATUS.has(err.status) || err.code === 'ECONNRESET';
482
+ if (!isRead || !transient) throw err;
483
+ await new Promise((r) => setTimeout(r, 300));
484
+ return this.#post(query, variables);
485
+ }
486
+ }
178
487
 
488
+ async #post(query, variables) {
179
489
  let response;
180
-
181
490
  try {
182
- response = await axios.post(this.endpoint, payload, { headers: this.headers });
491
+ response = await httpClient.post(this.endpoint, { query, variables }, { headers: this.headers });
183
492
  } catch (err) {
184
493
  if (err.response) {
494
+ const status = err.response.status;
185
495
  const e = new Error('GraphQL request failed');
186
- e.status = err.response.status;
496
+ e.status = status;
497
+ if (status === 401 || status === 403) {
498
+ e.authHint = 'Authentication failed — check that CONVISO_API_KEY is set to a valid Conviso Platform API key.';
499
+ }
187
500
  throw e;
188
501
  }
189
502
 
@@ -193,7 +506,7 @@ class GraphQLClient {
193
506
  throw e;
194
507
  }
195
508
 
196
- if (err.code === 'ETIMEDOUT') {
509
+ if (err.code === 'ETIMEDOUT' || err.code === 'ECONNABORTED') {
197
510
  const e = new Error('Upstream request timeout');
198
511
  e.status = 504;
199
512
  throw e;
@@ -203,53 +516,21 @@ class GraphQLClient {
203
516
  }
204
517
 
205
518
  if (response.data?.errors) {
519
+ const messages = response.data.errors.map((x) => x?.message).filter(Boolean);
206
520
  const e = new Error('GraphQL error');
207
521
  e.status = 400;
522
+ // Validation/field errors about the caller's own request — safe and useful to surface
523
+ // so the model can correct the input (e.g. a missing required mutation field).
524
+ e.graphqlErrors = messages;
208
525
  throw e;
209
526
  }
210
527
 
211
528
  return response.data.data;
212
529
  }
213
530
 
214
- async get_issues(company_id, search, page = 1, limit = 1, project_id = null, issue_ids = [], asset_ids = []) {
215
- const query = `
216
- query GetIssues($companyId: ID!, $pagination: PaginationInput!, $filters: IssuesFiltersInput) {
217
- issues(companyId: $companyId, pagination: $pagination, filters: $filters) {
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
- }
251
-
252
- return this.execute(query, variables);
531
+ async getIssues(companyId, opts = {}) {
532
+ const variables = buildIssuesVariables(companyId, opts.page || 1, opts.limit || 10, opts);
533
+ return this.execute(ISSUES_QUERY, variables);
253
534
  }
254
535
 
255
536
  async get_issue_by_id(issue_id, return_snippets = false) {
@@ -265,56 +546,15 @@ class GraphQLClient {
265
546
  return this.execute(query, variables);
266
547
  }
267
548
 
268
- async get_companies(page = 1, limit = 10, search = '') {
269
- const query = `
270
- query companies($page: Int, $limit: Int, $params: CompanySearch, $order: OrderScopesParams, $orderType: OrderParams){
271
- companies(page: $page, limit: $limit, params: $params, order: $order, orderType : $orderType) {
272
- collection {
273
- id
274
- label
275
- }
276
- }
277
- }
278
- `;
279
- const variables = { page, limit, params: { labelCont: search } };
280
- return this.execute(query, variables);
549
+ async get_companies(page = 1, limit = 10, search = '', label_eq = null) {
550
+ const params = F.prune({ labelCont: search, labelEq: label_eq });
551
+ const variables = { page, limit, params };
552
+ return this.execute(COMPANIES_QUERY, variables);
281
553
  }
282
554
 
283
- async get_projects(company_id, page = 1, limit = 1000, search = '') {
284
- const query = `
285
- query projects($page: Int, $limit: Int, $params: ProjectSearch, $sortBy: String, $descending: Boolean){
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);
555
+ async get_projects(company_id, page = 1, limit = 1000, search = '', opts = {}) {
556
+ const variables = buildProjectsVariables(company_id, page, limit, { search, ...opts });
557
+ return this.execute(PROJECTS_QUERY, variables);
318
558
  }
319
559
 
320
560
  async get_project_by_id(project_id) {
@@ -356,30 +596,9 @@ class GraphQLClient {
356
596
  return this.execute(query, variables);
357
597
  }
358
598
 
359
- async get_assets_by_company(company_id, page = 1, limit = 10) {
360
- const query = `
361
- query ListAssets($companyId: ID!, $page: Int, $limit: Int) {
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);
599
+ async get_assets_by_company(company_id, page = 1, limit = 10, opts = {}) {
600
+ const variables = buildAssetsVariables(company_id, page, limit, opts);
601
+ return this.execute(ASSETS_QUERY, variables);
383
602
  }
384
603
 
385
604
  async get_asset_by_id(asset_id) {
@@ -412,10 +631,10 @@ class GraphQLClient {
412
631
  return this.execute(query, variables);
413
632
  }
414
633
 
415
- async get_top_vulnerabilities(company_id) {
634
+ async get_top_vulnerabilities(company_id, opts = {}) {
416
635
  const query = `
417
- query TopVulnerabilities($companyId: ID!) {
418
- topVulnerabilities(companyId: $companyId) {
636
+ query TopVulnerabilities($companyId: ID!, $filters: TopVulnerabilitiesFiltersInput) {
637
+ topVulnerabilities(companyId: $companyId, filters: $filters) {
419
638
  affectedAssetsCount
420
639
  criticalCount
421
640
  highCount
@@ -426,7 +645,7 @@ class GraphQLClient {
426
645
  }
427
646
  }
428
647
  `;
429
- const variables = { companyId: company_id };
648
+ const variables = buildTopVulnsVariables(company_id, opts);
430
649
  return this.execute(query, variables);
431
650
  }
432
651
 
@@ -481,54 +700,211 @@ class GraphQLClient {
481
700
  return this.execute(query, variables);
482
701
  }
483
702
 
484
- async generate_project_report(project_id, language = 'en', vulnerability_criticity = null, vulnerability_statuses = null, requirements = true, evidences = true) {
703
+ // --- Mutations -------------------------------------------------------------
704
+
705
+ // Generic engine: run any catalogued mutation. Builds the GraphQL document from the
706
+ // SDL-derived catalog (whitelist) and executes it. `variables` is typically { input }.
707
+ async executeMutation(name, variables = {}, returnFields = null) {
708
+ const built = buildMutationQuery(name, variables, returnFields);
709
+ return this.execute(built.query, built.variables);
710
+ }
711
+
712
+ // Curated shortcuts for the most common writes (thin wrappers over executeMutation).
713
+ async change_issue_status(a = {}) {
714
+ return this.executeMutation('changeIssueStatus', buildChangeIssueStatusInput(a));
715
+ }
716
+
717
+ async create_source_code_vulnerability(a = {}) {
718
+ return this.executeMutation('createSourceCodeVulnerability', buildSourceCodeVulnerabilityInput(a));
719
+ }
720
+
721
+ async create_project(a = {}) {
722
+ return this.executeMutation('createProject', buildCreateProjectInput(a));
723
+ }
724
+
725
+ async create_asset(a = {}) {
726
+ return this.executeMutation('createAsset', buildCreateAssetInput(a));
727
+ }
728
+
729
+ async create_ticket(a = {}) {
730
+ return this.executeMutation('createTicket', buildCreateTicketInput(a));
731
+ }
732
+
733
+ // --- Curated DAST / AI-Pentest shortcuts ----------------------------------
734
+ async run_dast(a = {}) {
735
+ return this.executeMutation('startConvisoDast', { input: compact({ assetId: a.asset_id, ...(a.extra || {}) }) });
736
+ }
737
+
738
+ async trigger_pentest(a = {}) {
739
+ return this.executeMutation('createPentestExecution', { input: compact({ artifactId: a.artifact_id, ...(a.extra || {}) }) });
740
+ }
741
+
742
+ async create_pentest_artifact(a = {}) {
743
+ return this.executeMutation('createPentestArtifact', buildCreatePentestArtifactInput(a));
744
+ }
745
+
746
+ // --- Read queries (curated) ------------------------------------------------
747
+ async get_tickets(company_id, { page = 1, limit = 25, search, sort_by, descending, params } = {}) {
485
748
  const query = `
486
- query GenerateProjectReport(
487
- $projectId: ID!,
488
- $language: String!,
489
- $vulnerabilityCriticity: [SeverityCategory!],
490
- $vulnerabilityStatuses: [IssueStatusLabel!],
491
- $requirements: Boolean!,
492
- $evidences: Boolean!
493
- ) {
494
- generateProjectReport(
495
- projectId: $projectId,
496
- language: $language,
497
- vulnerabilityCriticity: $vulnerabilityCriticity,
498
- vulnerabilityStatuses: $vulnerabilityStatuses,
499
- requirements: $requirements,
500
- evidences: $evidences
501
- ) {
502
- id
503
- reportUrl
504
- status
505
- }
749
+ query GetTickets($companyId: ID!, $page: Int, $limit: Int, $sortBy: String, $descending: Boolean, $params: TicketSearch) {
750
+ tickets(companyId: $companyId, page: $page, limit: $limit, sortBy: $sortBy, descending: $descending, params: $params) {
751
+ collection { id title type status priority impact createdAt updatedAt createdBy { name email } assignee { name email } }
752
+ metadata { totalCount totalPages currentPage limitValue }
506
753
  }
507
- `;
508
- const variables = {
509
- projectId: project_id,
510
- language,
511
- vulnerabilityCriticity: vulnerability_criticity || ["CRITICAL", "HIGH", "MEDIUM", "LOW", "NOTIFICATION"],
512
- vulnerabilityStatuses: vulnerability_statuses || ["IDENTIFIED", "IN_PROGRESS", "AWAITING_VALIDATION", "FIX_ACCEPTED", "RISK_ACCEPTED", "FALSE_POSITIVE"],
513
- requirements,
514
- evidences
515
- };
516
- return this.execute(query, variables);
754
+ }`;
755
+ const p = compact({ search, ...(params || {}) });
756
+ return this.execute(query, { companyId: company_id, page, limit, sortBy: sort_by, descending, params: Object.keys(p).length ? p : undefined });
517
757
  }
518
758
 
519
- async generate_project_report_progress(project_id, report_id) {
759
+ async get_ticket(company_id, ticket_id) {
520
760
  const query = `
521
- query GenerateProjectReport($projectId: ID!, $reportId: ID!) {
522
- projectReport(projectId: $projectId, reportId: $reportId) {
523
- id
524
- progress
525
- reportUrl
526
- status
527
- }
761
+ query GetTicket($companyId: ID!, $id: ID!) {
762
+ ticket(companyId: $companyId, id: $id) {
763
+ id title description type status priority impact createdAt updatedAt
764
+ createdBy { name email } assignee { name email }
528
765
  }
529
- `;
530
- const variables = { projectId: project_id, reportId: report_id };
531
- return this.execute(query, variables);
766
+ }`;
767
+ return this.execute(query, { companyId: company_id, id: ticket_id });
768
+ }
769
+
770
+ async get_requirements(scope_id, { page = 1, limit = 25, filters } = {}) {
771
+ const query = `
772
+ query GetRequirements($scopeId: Int!, $pagination: BasePaginationInput!, $filters: RequirementsFilterInput) {
773
+ requirements(scopeId: $scopeId, pagination: $pagination, filters: $filters) {
774
+ collection { id label description global createdAt updatedAt }
775
+ metadata { totalCount totalPages currentPage limitValue }
776
+ }
777
+ }`;
778
+ return this.execute(query, { scopeId: scope_id, pagination: { page, perPage: limit }, filters });
779
+ }
780
+
781
+ async get_requirement(company_id, requirement_id) {
782
+ const query = `
783
+ query GetRequirement($companyId: ID!, $id: ID!) {
784
+ requirement(companyId: $companyId, id: $id) {
785
+ id label description global createdAt updatedAt
786
+ }
787
+ }`;
788
+ return this.execute(query, { companyId: company_id, id: requirement_id });
789
+ }
790
+
791
+ async get_project_requirements(project_id) {
792
+ const query = `
793
+ query GetProjectRequirements($projectId: ID!) {
794
+ projectRequirements(projectId: $projectId) {
795
+ id label description createdAt updatedAt
796
+ }
797
+ }`;
798
+ return this.execute(query, { projectId: project_id });
799
+ }
800
+
801
+ async get_applications(company_id, search = null) {
802
+ const query = `
803
+ query GetApplications($companyId: ID!, $search: String) {
804
+ applications(companyId: $companyId, search: $search) {
805
+ id name description url riskScore assetsCount createdAt updatedAt
806
+ }
807
+ }`;
808
+ return this.execute(query, { companyId: company_id, search });
809
+ }
810
+
811
+ async get_application(company_id, application_id) {
812
+ const query = `
813
+ query GetApplication($companyId: ID!, $id: ID!) {
814
+ application(id: $id, companyId: $companyId) {
815
+ id name description url riskScore assetsCount createdAt updatedAt
816
+ assets { id name }
817
+ }
818
+ }`;
819
+ return this.execute(query, { companyId: company_id, id: application_id });
820
+ }
821
+
822
+ async get_scan_histories(company_id, { assetIds, page = 1, limit = 25, filters, sortOptions } = {}) {
823
+ const query = `
824
+ query GetScanHistories($companyId: ID!, $assetIds: [ID!], $pagination: PaginationInput!, $filters: ScansHistoriesFiltersInput, $sortOptions: [ScansHistoriesSortOptionInput!]) {
825
+ scanHistories(companyId: $companyId, assetIds: $assetIds, pagination: $pagination, filters: $filters, sortOptions: $sortOptions) {
826
+ collection { id status integration createdAt durationInSeconds createdVulnerabilityCount closedVulnerabilityCount importedVulnerabilityCount failureReason asset { id name } }
827
+ metadata { totalCount totalPages currentPage limitValue }
828
+ }
829
+ }`;
830
+ return this.execute(query, { companyId: company_id, assetIds, pagination: { page, perPage: limit }, filters, sortOptions });
831
+ }
832
+
833
+ async get_asset_scans_count(company_id) {
834
+ const query = `
835
+ query GetAssetScansCount($companyId: ID!) {
836
+ assetScansCount(companyId: $companyId) { assetsWithScans assetsWithoutScans consideredScans }
837
+ }`;
838
+ return this.execute(query, { companyId: company_id });
839
+ }
840
+
841
+ async get_sbom_components(company_id, { page = 1, limit = 25, search } = {}) {
842
+ const query = `
843
+ query GetSbomComponents($companyId: ID!, $page: Int, $limit: Int, $search: SbomComponentSearchInput) {
844
+ sbomComponents(companyId: $companyId, page: $page, limit: $limit, search: $search) {
845
+ collection { id name version technology packageManager license issuesBySeverity asset { id name } createdAt updatedAt }
846
+ metadata { totalCount totalPages currentPage limitValue }
847
+ }
848
+ }`;
849
+ return this.execute(query, { companyId: company_id, page, limit, search });
850
+ }
851
+
852
+ async get_pentest_artifacts(company_id, { page = 1, limit = 25, search, assigneeEmail, pentestType, applicationId } = {}) {
853
+ const query = `
854
+ query GetPentestArtifacts($companyId: ID!, $pagination: BasePaginationInput!, $search: String, $assigneeEmail: String, $pentestType: String, $applicationId: ID) {
855
+ pentestArtifacts(companyId: $companyId, pagination: $pagination, search: $search, assigneeEmail: $assigneeEmail, pentestType: $pentestType, applicationId: $applicationId) {
856
+ collection { id label description pentestType createdAt updatedAt useScheduling scheduledAt executionsCount assignee { name email } application { id name } latestExecution { id status runNumber vulnerabilitiesCount createdAt } }
857
+ metadata { totalCount totalPages currentPage limitValue }
858
+ }
859
+ }`;
860
+ return this.execute(query, { companyId: company_id, pagination: { page, perPage: limit }, search, assigneeEmail, pentestType, applicationId });
861
+ }
862
+
863
+ async get_pentest_artifact(artifact_id) {
864
+ const query = `
865
+ query GetPentestArtifact($id: ID!) {
866
+ pentestArtifact(id: $id) {
867
+ id label description pentestType scopeText inScope outScope domains createdAt updatedAt useScheduling scheduledAt
868
+ assignee { name email } application { id name }
869
+ executions { id status runNumber vulnerabilitiesCount createdAt }
870
+ }
871
+ }`;
872
+ return this.execute(query, { id: artifact_id });
873
+ }
874
+
875
+ async get_pentest_execution(execution_id) {
876
+ const query = `
877
+ query GetPentestExecution($id: ID!) {
878
+ pentestExecution(id: $id) {
879
+ id status kind triggerKind runNumber startedAt finishedAt durationSeconds
880
+ vulnerabilitiesCount severityBreakdown nodeCount retestFixedCount retestTotalCount
881
+ project { id label } pentestArtifact { id label } triggeredBy { name email }
882
+ }
883
+ }`;
884
+ return this.execute(query, { id: execution_id });
885
+ }
886
+
887
+ async get_threat_model_artifacts(company_id, { page = 1, limit = 25, search, assigneeEmail, hasVersion } = {}) {
888
+ const query = `
889
+ query GetThreatModelArtifacts($companyId: ID!, $pagination: BasePaginationInput!, $search: String, $assigneeEmail: String, $hasVersion: Boolean) {
890
+ threatModelArtifacts(companyId: $companyId, pagination: $pagination, search: $search, assigneeEmail: $assigneeEmail, hasVersion: $hasVersion) {
891
+ collection { id label description scopeText createdAt updatedAt assignee { name email } latestVersion { id version createdAt } }
892
+ metadata { totalCount totalPages currentPage limitValue }
893
+ }
894
+ }`;
895
+ return this.execute(query, { companyId: company_id, pagination: { page, perPage: limit }, search, assigneeEmail, hasVersion });
896
+ }
897
+
898
+ async get_threat_model_artifact(artifact_id) {
899
+ const query = `
900
+ query GetThreatModelArtifact($id: ID!) {
901
+ threatModelArtifact(id: $id) {
902
+ id label description scopeText createdAt updatedAt
903
+ assignee { name email }
904
+ versions { id version createdAt diagramType scopeText notes }
905
+ }
906
+ }`;
907
+ return this.execute(query, { id: artifact_id });
532
908
  }
533
909
  }
534
910