@arcadialdev/arcality 2.4.24 → 2.4.26

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,5 +1,49 @@
1
1
  import { Page, TestInfo } from '@playwright/test';
2
2
  import { qaAdvancedTools } from './qa-tools';
3
+ import {
4
+ test_xss_injection,
5
+ test_auth_bypass,
6
+ audit_http_headers,
7
+ scan_sensitive_data_exposure,
8
+ SecurityFinding
9
+ } from './qa-security-tools';
10
+
11
+ const qaSecurityTools = [
12
+ {
13
+ name: "test_xss_injection",
14
+ description: "Injects a standard XSS payload into an input field to test for reflected XSS vulnerabilities. Reports a vulnerability if an alert is triggered.",
15
+ parameters: {
16
+ type: "object",
17
+ properties: {
18
+ selector: { type: "string", description: "The CSS selector of the input field to test." }
19
+ },
20
+ required: ["selector"]
21
+ }
22
+ },
23
+ {
24
+ name: "test_auth_bypass",
25
+ description: "Attempts to directly navigate to a URL that should be protected to check for authentication bypass vulnerabilities.",
26
+ parameters: {
27
+ type: "object",
28
+ properties: {
29
+ url: { type: "string", description: "The protected URL to test." },
30
+ redirectUrl: { type: "string", description: "The expected URL to be redirected to if unauthorized (e.g., '/login')." }
31
+ },
32
+ required: ["url", "redirectUrl"]
33
+ }
34
+ },
35
+ {
36
+ name: "audit_http_headers",
37
+ description: "Audits the current page's HTTP response headers for common security headers like CSP, HSTS, etc.",
38
+ parameters: { type: "object", properties: {} }
39
+ },
40
+ {
41
+ name: "scan_sensitive_data_exposure",
42
+ description: "Scans localStorage and sessionStorage for keys that might contain sensitive data (e.g., 'token', 'password').",
43
+ parameters: { type: "object", properties: {} }
44
+ }
45
+ ];
46
+ import { SecurityScanner } from '../../src/services/securityScanner';
3
47
  import chalk from 'chalk';
4
48
  import * as fs from 'fs';
5
49
  import * as path from 'path';
@@ -478,8 +522,12 @@ ${best.steps.slice(0, 15).map((s: any) => ` - ${s}`).join('\n')}
478
522
  const memStep = m.steps_data[historyLength];
479
523
  if (!memStep) return false;
480
524
 
481
- const memUrl = (memStep.url || '').split('?')[0];
482
- const currUrl = currentUrl.split('?')[0];
525
+ const normalizeUrl = (u: string) =>
526
+ u.replace(/\/[a-z]{2}-[A-Z]{2}\//g, '/{locale}/')
527
+ .replace(/\/[a-z]{2}\//g, '/{lang}/');
528
+
529
+ const memUrl = normalizeUrl((memStep.url || '').split('?')[0]);
530
+ const currUrl = normalizeUrl(currentUrl.split('?')[0]);
483
531
 
484
532
  // URL Validation
485
533
  if (memUrl !== currUrl) return false;
@@ -539,7 +587,7 @@ ${best.steps.slice(0, 15).map((s: any) => ` - ${s}`).join('\n')}
539
587
  return null;
540
588
  }
541
589
 
542
- async askIA(prompt: string, history: string[] = []): Promise<AgentAction> {
590
+ async askIA(prompt: string, history: string[] = [], stepCount?: number): Promise<AgentAction> {
543
591
  // El modo proxy (ARCALITY_API_URL) está integrado en el bucle de fetch de abajo.
544
592
  // Si ARCALITY_API_URL está definido, se enruta automáticamente a /api/v1/ai/proxy.
545
593
  // Si no, requiere ANTHROPIC_API_KEY para llamada directa.
@@ -632,6 +680,11 @@ ${best.steps.slice(0, 15).map((s: any) => ` - ${s}`).join('\n')}
632
680
  name: t.name,
633
681
  description: t.description,
634
682
  parameters: (t as any).input_schema || (t as any).parameters
683
+ })),
684
+ ...qaSecurityTools.map(t => ({
685
+ name: t.name,
686
+ description: t.description,
687
+ parameters: (t as any).input_schema || (t as any).parameters
635
688
  }))
636
689
  ];
637
690
 
@@ -828,8 +881,8 @@ ${history.slice(-25).join('\n') || 'None'}`
828
881
  ? `${process.env.ARCALITY_API_URL}/api/v1/ai/proxy`
829
882
  : "https://api.anthropic.com/v1/messages";
830
883
 
831
- // Log the endpoint being called on every turn so we can debug connectivity
832
- console.log(`>>ARCALITY_STATUS>> 📡 Llamando a: ${endpointUrl} (turno ${turn + 1})`);
884
+ const displayTurn = stepCount !== undefined ? stepCount : (turn + 1);
885
+ console.log(`>>ARCALITY_STATUS>> 📡 Llamando a: ${endpointUrl} (turno IA ${displayTurn}${turn > 0 ? `, reintento ${turn}` : ''})`);
833
886
 
834
887
  const headers: Record<string, string> = {
835
888
  "Content-Type": "application/json"
@@ -837,6 +890,8 @@ ${history.slice(-25).join('\n') || 'None'}`
837
890
 
838
891
  if (isProxyMode) {
839
892
  headers["x-api-key"] = process.env.ARCALITY_API_KEY || "";
893
+ const missionId = process.env.ARCALITY_MISSION_ID || '';
894
+ if (missionId) headers["x-mission-id"] = missionId;
840
895
  } else {
841
896
  headers["x-api-key"] = process.env.ANTHROPIC_API_KEY!;
842
897
  headers["anthropic-version"] = "2023-06-01";
@@ -1162,7 +1217,68 @@ ${history.slice(-25).join('\n') || 'None'}`
1162
1217
  toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Error restoring checkpoint: ${e.message}` });
1163
1218
  }
1164
1219
  }
1165
-
1220
+ } else if (toolName === "test_xss_injection") {
1221
+ const { idx, payload_type } = toolInput;
1222
+ const el = state.components.find(c => c.idx === idx);
1223
+ if (!el) {
1224
+ toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Element idx ${idx} not found.` });
1225
+ } else {
1226
+ console.log(`>>ARCALITY_STATUS>> 🛡️ [Security] Probando inyección XSS en IDX ${idx}...`);
1227
+ let payload = "<script>alert(1)</script>";
1228
+ if (payload_type === 'image') payload = "<img src=x onerror=alert(1)>";
1229
+ if (payload_type === 'svg') payload = "<svg onload=alert(1)>";
1230
+
1231
+ try {
1232
+ const locator = this.page.locator(`xpath=${el.selector}`).first();
1233
+ await locator.fill(payload);
1234
+ toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Payload inyectado. Asegúrate de probar si el payload fue ejecutado revisando el DOM, o realiza el sumbit y observa si el input regresó renderizado tal cual o sanitizado.` });
1235
+ if (this.testInfo) this.testInfo.attach('security_tool_call', { body: JSON.stringify({ action: "test_xss_injection", target: idx, payload }), contentType: "application/json" });
1236
+ } catch (e: any) {
1237
+ toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Error injecting payload: ${e.message}` });
1238
+ }
1239
+ }
1240
+ } else if (toolName === "test_auth_bypass") {
1241
+ const { target_url } = toolInput;
1242
+ console.log(`>>ARCALITY_STATUS>> 🛡️ [Security] Intentando Auth Bypass en ${target_url}...`);
1243
+ try {
1244
+ const baseUrl = new URL(this.page.url()).origin;
1245
+ const fullUrl = new URL(target_url, baseUrl).toString();
1246
+ await this.page.goto(fullUrl);
1247
+ await this.page.waitForLoadState('networkidle', { timeout: 3000 }).catch(() => {});
1248
+ const resultingUrl = this.page.url();
1249
+ let resultData = `Navegado a ${fullUrl}. \nURL resultante: ${resultingUrl}. \n`;
1250
+ if (resultingUrl !== fullUrl) resultData += `Posiblemente redirigido (el bypass falló o te enviaron a Login).`;
1251
+ else resultData += `URL se mantuvo igual. ¡Esto podría ser un IDOR o Auth Bypass si ves datos a los que no deberías acceder!`;
1252
+ toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: resultData });
1253
+ } catch (e: any) {
1254
+ toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Error test_auth_bypass: ${e.message}` });
1255
+ }
1256
+ } else if (toolName === "scan_sensitive_data_exposure") {
1257
+ console.log(`>>ARCALITY_STATUS>> 🛡️ [Security] Escaneando datos sensibles...`);
1258
+ try {
1259
+ const issues = await scan_sensitive_data_exposure(this.page);
1260
+ if (issues.length > 0) {
1261
+ const report = issues.map((i: any) => `- ${i.category}: ${i.description}\n Evidence: ${JSON.stringify(i.evidence)}`).join('\n');
1262
+ toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Vulnerabilidades detectadas:\n${report}` });
1263
+ } else {
1264
+ toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: "No se detectaron datos sensibles en LocalStorage/SessionStorage en este chequeo." });
1265
+ }
1266
+ } catch (e: any) {
1267
+ toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Error scan_sensitive_data_exposure: ${e.message}` });
1268
+ }
1269
+ } else if (toolName === "audit_http_headers") {
1270
+ console.log(`>>ARCALITY_STATUS>> 🛡️ [Security] Auditando headers HTTP...`);
1271
+ try {
1272
+ const issues = await audit_http_headers(this.page);
1273
+ if (issues.length > 0) {
1274
+ const report = issues.map((i: any) => `- ${i.category}: ${i.description}`).join('\n');
1275
+ toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Vulnerabilidades de Headers detectadas:\n${report}` });
1276
+ } else {
1277
+ toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: "No se detectaron problemas en los headers de seguridad básicos." });
1278
+ }
1279
+ } catch (e: any) {
1280
+ toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Error audit_http_headers: ${e.message}` });
1281
+ }
1166
1282
  } else if (toolName === "create_test_evidence") {
1167
1283
  const { evidence_type, annotations, save_as, description, finish_run = false } = toolInput;
1168
1284
  console.log(`📸 [SKILL] Creating test evidence: "${save_as}"...`);
@@ -1385,6 +1501,8 @@ ${history.slice(-25).join('\n') || 'None'}`
1385
1501
  } catch (e: any) {
1386
1502
  toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Error scrolling: ${e.message}` });
1387
1503
  }
1504
+ } else {
1505
+ toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Error: Tool '${toolName}' not recognized by this version.` });
1388
1506
  }
1389
1507
  }
1390
1508
 
@@ -1438,6 +1556,8 @@ ${history.slice(-25).join('\n') || 'None'}`
1438
1556
 
1439
1557
  if (isProxyMode) {
1440
1558
  headers["x-api-key"] = process.env.ARCALITY_API_KEY || "";
1559
+ const missionId = process.env.ARCALITY_MISSION_ID || '';
1560
+ if (missionId) headers["x-mission-id"] = missionId;
1441
1561
  } else {
1442
1562
  headers["x-api-key"] = process.env.ANTHROPIC_API_KEY!;
1443
1563
  headers["anthropic-version"] = "2023-06-01";
@@ -1487,6 +1607,8 @@ ${history.slice(-25).join('\n') || 'None'}`
1487
1607
 
1488
1608
  if (isProxyMode) {
1489
1609
  headers["x-api-key"] = process.env.ARCALITY_API_KEY || "";
1610
+ const missionId = process.env.ARCALITY_MISSION_ID || '';
1611
+ if (missionId) headers["x-mission-id"] = missionId;
1490
1612
  } else {
1491
1613
  headers["x-api-key"] = process.env.ANTHROPIC_API_KEY!;
1492
1614
  headers["anthropic-version"] = "2023-06-01";
@@ -0,0 +1,265 @@
1
+ /* eslint-disable no-console */
2
+ import { Page, expect } from '@playwright/test';
3
+
4
+ export type Severity = 'Critical' | 'High' | 'Medium' | 'Low' | 'Info';
5
+
6
+ export type SecurityFindingType = 'confirmed_vulnerability' | 'security_misconfiguration' | 'potential_issue' | 'informational';
7
+ export type Confidence = 'High' | 'Medium' | 'Low';
8
+ export type Exploitability = 'confirmed' | 'likely' | 'unknown' | 'not_confirmed';
9
+
10
+ export interface SecurityFinding {
11
+ type: SecurityFindingType;
12
+ category: string;
13
+ subcategory?: string;
14
+ severity: Severity;
15
+ confidence: Confidence;
16
+ exploitability: Exploitability;
17
+ status: 'detected';
18
+ description: string;
19
+ impact?: string;
20
+ evidence: {
21
+ [key: string]: any;
22
+ };
23
+ reproductionSteps?: string[];
24
+ remediation: string;
25
+ classification?: {
26
+ owaspTop10?: string;
27
+ cwe?: string;
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Attempts to inject a simple XSS payload into a specified element and checks for an alert.
33
+ * @param page - The Playwright Page object.
34
+ * @param selector - The selector for the input field to test.
35
+ * @returns A vulnerability object if successful, null otherwise.
36
+ */
37
+ export async function test_xss_injection(
38
+ page: Page,
39
+ selector: string,
40
+ ): Promise<SecurityFinding | null> {
41
+ const payload = `<script>alert('XSS-ARCALITY-TEST')</script>`;
42
+ console.log(`[QA-SEC] Testing XSS on selector: ${selector}`);
43
+
44
+ let alertTriggered = false;
45
+ page.once('dialog', async (dialog) => {
46
+ if (dialog.message().includes('XSS-ARCALITY-TEST')) {
47
+ alertTriggered = true;
48
+ }
49
+ await dialog.dismiss();
50
+ });
51
+
52
+ await page.fill(selector, payload);
53
+ // Submit the form or trigger the action that would process the input
54
+ // This part is crucial and might need to be adapted based on the application's behavior
55
+ await page.press(selector, 'Enter');
56
+
57
+ // Wait a moment for any potential script execution
58
+ await page.waitForTimeout(1000);
59
+
60
+ if (alertTriggered) {
61
+ console.log(`[QA-SEC] XSS Vulnerability DETECTED at ${selector}`);
62
+ return {
63
+ type: 'confirmed_vulnerability',
64
+ category: 'Injection',
65
+ subcategory: 'Reflected XSS',
66
+ severity: 'High',
67
+ confidence: 'High',
68
+ exploitability: 'confirmed',
69
+ status: 'detected',
70
+ description: `A reflected Cross-Site Scripting (XSS) vulnerability was detected in the element '${selector}'. The application executed a script injected into this input field.`,
71
+ impact: 'Allows execution of arbitrary JavaScript in the context of the user session, potentially leading to session hijacking, credential theft, or unauthorized actions.',
72
+ evidence: {
73
+ payload,
74
+ url: page.url(),
75
+ selector,
76
+ },
77
+ reproductionSteps: [
78
+ `Navigate to the page containing '${selector}'.`,
79
+ `Inject the payload: ${payload}`,
80
+ 'Trigger the form submission or blur event.',
81
+ 'Observe the script execution (alert dialog).'
82
+ ],
83
+ remediation: 'Sanitize all user-supplied input on the server-side before rendering it back to the page. Use libraries like DOMPurify on the client-side as an additional layer of defense.',
84
+ classification: {
85
+ owaspTop10: 'A03:2021 Injection',
86
+ cwe: 'CWE-79'
87
+ }
88
+ };
89
+ }
90
+
91
+ console.log(`[QA-SEC] No XSS detected for selector: ${selector}`);
92
+ return null;
93
+ }
94
+
95
+ /**
96
+ * Checks if a given URL, assumed to be protected, can be accessed without authentication.
97
+ * @param page - The Playwright Page object.
98
+ * @param url - The protected URL to test.
99
+ * @param redirectUrl - The URL the app should redirect to if unauthorized.
100
+ * @returns A vulnerability object if bypass is successful, null otherwise.
101
+ */
102
+ export async function test_auth_bypass(
103
+ page: Page,
104
+ url: string,
105
+ redirectUrl: string | RegExp,
106
+ ): Promise<SecurityFinding | null> {
107
+ console.log(`[QA-SEC] Testing Auth Bypass on URL: ${url}`);
108
+ const response = await page.goto(url, { waitUntil: 'domcontentloaded' });
109
+
110
+ // If the page loads the protected content (doesn't redirect or redirects to a different page)
111
+ if (response?.ok() && !page.url().match(redirectUrl)) {
112
+ console.log(`[QA-SEC] Auth Bypass DETECTED for URL: ${url}`);
113
+ return {
114
+ type: 'confirmed_vulnerability',
115
+ category: 'Broken Access Control',
116
+ subcategory: 'Authentication Bypass',
117
+ severity: 'Critical',
118
+ confidence: 'High',
119
+ exploitability: 'confirmed',
120
+ status: 'detected',
121
+ description: `Unauthorized access to the protected route '${url}' was successful. The application failed to enforce authentication, allowing a direct bypass to sensitive content.`,
122
+ impact: 'An unauthenticated attacker can access protected resources, potentially reading or modifying sensitive user data.',
123
+ evidence: {
124
+ accessedUrl: url,
125
+ finalUrl: page.url(),
126
+ },
127
+ reproductionSteps: [
128
+ 'Ensure no active session (cleared cookies/storage).',
129
+ `Navigate directly to: ${url}`,
130
+ 'Observe that the protected page loads without redirection.'
131
+ ],
132
+ remediation: 'Implement server-side middleware to verify user authentication and authorization status before serving any protected routes.',
133
+ classification: {
134
+ owaspTop10: 'A01:2021 Broken Access Control',
135
+ cwe: 'CWE-285'
136
+ }
137
+ };
138
+ }
139
+
140
+ console.log(`[QA-SEC] No Auth Bypass detected for URL: ${url}`);
141
+ return null;
142
+ }
143
+
144
+ /**
145
+ * Audits the HTTP security headers of the current page.
146
+ * @param page - The Playwright Page object.
147
+ * @returns An array of vulnerability objects for missing headers.
148
+ */
149
+ export async function audit_http_headers(page: Page): Promise<SecurityFinding[]> {
150
+ console.log(`[QA-SEC] Auditing HTTP Headers for: ${page.url()}`);
151
+ const response = await page.reload({ waitUntil: 'domcontentloaded' });
152
+ const headers = response?.headers();
153
+ const vulnerabilities: SecurityFinding[] = [];
154
+
155
+ const securityHeaders = {
156
+ 'Content-Security-Policy': {
157
+ severity: 'Medium',
158
+ remediation: 'Implement a strict Content-Security-Policy (CSP) to mitigate XSS and other injection attacks. Start with a restrictive policy and gradually allow trusted sources.',
159
+ impact: 'If an injection issue exists elsewhere in the application, the lack of CSP may increase exploitability and impact.',
160
+ cwe: 'CWE-16'
161
+ },
162
+ 'Strict-Transport-Security': {
163
+ severity: 'Medium',
164
+ remediation: 'Implement HTTP Strict Transport Security (HSTS) to force browsers to communicate over HTTPS, preventing downgrade attacks.',
165
+ impact: 'Man-in-the-middle attacks could downgrade the connection to HTTP, allowing traffic interception.',
166
+ cwe: 'CWE-319'
167
+ },
168
+ 'X-Content-Type-Options': {
169
+ severity: 'Low',
170
+ remediation: 'Set `X-Content-Type-Options: nosniff` to prevent browsers from MIME-sniffing a response away from the declared content-type.',
171
+ impact: 'Browsers may interpret non-executable MIME types as executable, leading to potential XSS.',
172
+ cwe: 'CWE-430'
173
+ },
174
+ 'X-Frame-Options': {
175
+ severity: 'Medium',
176
+ remediation: 'Set `X-Frame-Options: SAMEORIGIN` or `DENY` to protect against clickjacking attacks by preventing the page from being loaded in an iframe on other domains.',
177
+ impact: 'Attackers could trick users into clicking visually hidden elements, causing unintended actions (Clickjacking).',
178
+ cwe: 'CWE-1021'
179
+ }
180
+ };
181
+
182
+ for (const [header, info] of Object.entries(securityHeaders)) {
183
+ if (!headers || !Object.keys(headers).find(h => h.toLowerCase() === header.toLowerCase())) {
184
+ vulnerabilities.push({
185
+ type: 'security_misconfiguration',
186
+ category: 'Missing Security Header',
187
+ subcategory: header,
188
+ severity: info.severity as Severity,
189
+ confidence: 'High',
190
+ exploitability: 'not_confirmed',
191
+ status: 'detected',
192
+ description: `The '${header}' HTTP header is missing. This header is crucial for defending against various web attacks.`,
193
+ impact: info.impact,
194
+ evidence: {
195
+ url: page.url(),
196
+ missingHeader: header
197
+ },
198
+ reproductionSteps: [
199
+ `Navigate to ${page.url()}`,
200
+ `Inspect the response headers.`,
201
+ `Verify that ${header} is absent.`
202
+ ],
203
+ remediation: info.remediation,
204
+ classification: {
205
+ owaspTop10: 'A05:2021 Security Misconfiguration',
206
+ cwe: info.cwe
207
+ }
208
+ });
209
+ }
210
+ }
211
+
212
+ console.log(`[QA-SEC] Found ${vulnerabilities.length} missing security headers.`);
213
+ return vulnerabilities;
214
+ }
215
+
216
+ /**
217
+ * Scans localStorage and sessionStorage for potentially sensitive data.
218
+ * @param page - The Playwright Page object.
219
+ * @returns An array of vulnerability objects for any sensitive data found.
220
+ */
221
+ export async function scan_sensitive_data_exposure(page: Page): Promise<SecurityFinding[]> {
222
+ console.log(`[QA-SEC] Scanning localStorage and sessionStorage for sensitive data.`);
223
+ const vulnerabilities: SecurityFinding[] = [];
224
+ const sensitiveKeywords = ['token', 'password', 'secret', 'key', 'jwt', 'auth'];
225
+
226
+ const storages = {
227
+ localStorage: await page.evaluate(() => Object.entries(window.localStorage)),
228
+ sessionStorage: await page.evaluate(() => Object.entries(window.sessionStorage)),
229
+ };
230
+
231
+ for (const [storageType, items] of Object.entries(storages)) {
232
+ for (const [key, value] of items) {
233
+ if (sensitiveKeywords.some(keyword => key.toLowerCase().includes(keyword))) {
234
+ vulnerabilities.push({
235
+ type: 'potential_issue',
236
+ category: 'Sensitive Data Exposure',
237
+ subcategory: 'Browser Storage',
238
+ severity: 'Medium',
239
+ confidence: 'Medium',
240
+ exploitability: 'unknown',
241
+ status: 'detected',
242
+ description: `Potentially sensitive data was found in ${storageType}. Storing sensitive information like tokens or keys in browser storage is insecure.`,
243
+ impact: 'If an attacker discovers an XSS vulnerability, they can read localStorage/sessionStorage and exfiltrate secrets.',
244
+ evidence: {
245
+ storage: storageType,
246
+ key: key,
247
+ valuePreview: value ? `${value.substring(0, 10)}...` : '(empty)',
248
+ },
249
+ reproductionSteps: [
250
+ `Open Developer Tools on ${page.url()}`,
251
+ `Check ${storageType} for the key '${key}'.`
252
+ ],
253
+ remediation: 'Avoid storing sensitive data in browser storage. Use secure, HttpOnly cookies for session tokens. If you must store data, encrypt it first.',
254
+ classification: {
255
+ owaspTop10: 'A04:2021 Insecure Design',
256
+ cwe: 'CWE-312'
257
+ }
258
+ });
259
+ }
260
+ }
261
+ }
262
+
263
+ console.log(`[QA-SEC] Found ${vulnerabilities.length} instances of sensitive data in browser storage.`);
264
+ return vulnerabilities;
265
+ }