regscale-cli 6.20.10.0__py3-none-any.whl → 6.21.1.0__py3-none-any.whl
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.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/_version.py +1 -1
- regscale/core/app/application.py +12 -5
- regscale/core/app/internal/set_permissions.py +58 -27
- regscale/integrations/commercial/__init__.py +1 -2
- regscale/integrations/commercial/amazon/common.py +79 -2
- regscale/integrations/commercial/aws/cli.py +183 -9
- regscale/integrations/commercial/aws/scanner.py +544 -9
- regscale/integrations/commercial/cpe.py +18 -1
- regscale/integrations/commercial/nessus/scanner.py +2 -0
- regscale/integrations/commercial/sonarcloud.py +35 -36
- regscale/integrations/commercial/synqly/ticketing.py +51 -0
- regscale/integrations/commercial/tenablev2/jsonl_scanner.py +2 -1
- regscale/integrations/commercial/wizv2/async_client.py +10 -3
- regscale/integrations/commercial/wizv2/click.py +102 -26
- regscale/integrations/commercial/wizv2/constants.py +249 -1
- regscale/integrations/commercial/wizv2/issue.py +2 -2
- regscale/integrations/commercial/wizv2/parsers.py +3 -2
- regscale/integrations/commercial/wizv2/policy_compliance.py +1858 -0
- regscale/integrations/commercial/wizv2/scanner.py +15 -21
- regscale/integrations/commercial/wizv2/utils.py +258 -85
- regscale/integrations/commercial/wizv2/variables.py +4 -3
- regscale/integrations/compliance_integration.py +1455 -0
- regscale/integrations/integration_override.py +15 -6
- regscale/integrations/public/fedramp/fedramp_five.py +1 -1
- regscale/integrations/public/fedramp/markdown_parser.py +7 -1
- regscale/integrations/scanner_integration.py +193 -37
- regscale/models/app_models/__init__.py +1 -0
- regscale/models/integration_models/amazon_models/inspector_scan.py +32 -57
- regscale/models/integration_models/aqua.py +92 -78
- regscale/models/integration_models/cisa_kev_data.json +117 -5
- regscale/models/integration_models/defenderimport.py +64 -59
- regscale/models/integration_models/ecr_models/ecr.py +100 -147
- regscale/models/integration_models/flat_file_importer/__init__.py +52 -38
- regscale/models/integration_models/ibm.py +29 -47
- regscale/models/integration_models/nexpose.py +156 -68
- regscale/models/integration_models/prisma.py +46 -66
- regscale/models/integration_models/qualys.py +99 -93
- regscale/models/integration_models/snyk.py +229 -158
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/veracode.py +15 -20
- regscale/{integrations/commercial/wizv2/models.py → models/integration_models/wizv2.py} +4 -12
- regscale/models/integration_models/xray.py +276 -82
- regscale/models/regscale_models/control_implementation.py +14 -12
- regscale/models/regscale_models/file.py +4 -0
- regscale/models/regscale_models/issue.py +123 -0
- regscale/models/regscale_models/milestone.py +1 -1
- regscale/models/regscale_models/rbac.py +22 -0
- regscale/models/regscale_models/regscale_model.py +4 -2
- regscale/models/regscale_models/security_plan.py +1 -1
- regscale/utils/graphql_client.py +3 -1
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/METADATA +9 -9
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/RECORD +64 -60
- tests/fixtures/test_fixture.py +58 -2
- tests/regscale/core/test_app.py +5 -3
- tests/regscale/core/test_version_regscale.py +5 -3
- tests/regscale/integrations/test_integration_mapping.py +522 -40
- tests/regscale/integrations/test_issue_due_date.py +1 -1
- tests/regscale/integrations/test_update_finding_dates.py +336 -0
- tests/regscale/integrations/test_wiz_policy_compliance_affected_controls.py +154 -0
- tests/regscale/models/test_asset.py +406 -50
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/top_level.txt +0 -0
|
@@ -1270,6 +1270,129 @@ class Issue(RegScaleModel):
|
|
|
1270
1270
|
raise ValueError(f"riskAdjustment must be one of {allowed_values}")
|
|
1271
1271
|
return v
|
|
1272
1272
|
|
|
1273
|
+
# New method to determine and set isPoam based on NIST/FedRAMP criteria
|
|
1274
|
+
def set_is_poam(
|
|
1275
|
+
self,
|
|
1276
|
+
config: Optional[Dict[str, Any]] = None,
|
|
1277
|
+
standard: str = "fedramp",
|
|
1278
|
+
current_date: Optional[datetime] = None,
|
|
1279
|
+
) -> None:
|
|
1280
|
+
"""
|
|
1281
|
+
Sets the isPoam field based on NIST 800-53 or FedRAMP criteria, preserving historical POAM status.
|
|
1282
|
+
|
|
1283
|
+
Criteria:
|
|
1284
|
+
- Preserves isPoam=True for imported data, even if closed, for reporting purposes.
|
|
1285
|
+
- For new issues:
|
|
1286
|
+
- Skips if false positive, operational requirement, or deviation rationale exists.
|
|
1287
|
+
- FedRAMP: High/Critical issues are POAMs if open; scan-based issues are POAMs if overdue; non-scan issues are POAMs if open.
|
|
1288
|
+
- NIST: POAM for any open deficiency unless accepted as residual risk.
|
|
1289
|
+
- Uses config thresholds (e.g., {'critical': 30, 'high': 90, 'medium': 90, 'low': 365, 'status': 'Open'}).
|
|
1290
|
+
|
|
1291
|
+
Args:
|
|
1292
|
+
config: Optional dictionary with severity thresholds and status from init.yaml.
|
|
1293
|
+
Defaults to FedRAMP: {'critical': 30, 'high': 30, 'medium': 90, 'low': 180, 'status': 'Open'}.
|
|
1294
|
+
For NIST, uses {'critical': 30, 'high': 90, 'medium': 90, 'low': 180, 'status': 'Open'}.
|
|
1295
|
+
standard: 'fedramp' (default) or 'nist'.
|
|
1296
|
+
current_date: Optional datetime for calculation (defaults to current time).
|
|
1297
|
+
|
|
1298
|
+
Returns:
|
|
1299
|
+
None: Sets the isPoam attribute directly.
|
|
1300
|
+
"""
|
|
1301
|
+
# Use current time if not provided
|
|
1302
|
+
current_date = current_date or datetime.datetime.now()
|
|
1303
|
+
|
|
1304
|
+
# Preserve historical POAM status for imported data
|
|
1305
|
+
if self.isPoam:
|
|
1306
|
+
return
|
|
1307
|
+
|
|
1308
|
+
# Define open statuses
|
|
1309
|
+
open_statuses = {
|
|
1310
|
+
IssueStatus.Open,
|
|
1311
|
+
IssueStatus.Delayed,
|
|
1312
|
+
IssueStatus.PendingVerification,
|
|
1313
|
+
IssueStatus.VendorDependency,
|
|
1314
|
+
IssueStatus.PendingApproval,
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
# Skip if issue is accepted as residual risk
|
|
1318
|
+
if self.falsePositive or self.operationalRequirement or self.deviationRationale:
|
|
1319
|
+
self.isPoam = False
|
|
1320
|
+
return
|
|
1321
|
+
|
|
1322
|
+
# Load default thresholds based on standard if config is not provided
|
|
1323
|
+
config = config or (
|
|
1324
|
+
{"critical": 30, "high": 30, "medium": 90, "low": 180, "status": "Open"}
|
|
1325
|
+
if standard == "fedramp"
|
|
1326
|
+
else {"critical": 30, "high": 90, "medium": 90, "low": 180, "status": "Open"}
|
|
1327
|
+
)
|
|
1328
|
+
|
|
1329
|
+
# Map severity to remediation days
|
|
1330
|
+
severity_map = {
|
|
1331
|
+
IssueSeverity.Critical: config.get("critical", 30),
|
|
1332
|
+
IssueSeverity.High: config.get("high", 90),
|
|
1333
|
+
IssueSeverity.Moderate: config.get("medium", 90),
|
|
1334
|
+
IssueSeverity.Low: config.get("low", 365),
|
|
1335
|
+
IssueSeverity.NotAssigned: config.get("low", 365),
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
# Normalize severity
|
|
1339
|
+
severity = (
|
|
1340
|
+
IssueSeverity(self.severityLevel)
|
|
1341
|
+
if self.severityLevel in {s.value for s in IssueSeverity}
|
|
1342
|
+
else IssueSeverity.NotAssigned
|
|
1343
|
+
)
|
|
1344
|
+
threshold_days = severity_map[severity]
|
|
1345
|
+
|
|
1346
|
+
# Get detection date
|
|
1347
|
+
detection_date_str = self.dateFirstDetected or self.dateCreated
|
|
1348
|
+
if not detection_date_str:
|
|
1349
|
+
self.isPoam = False
|
|
1350
|
+
return
|
|
1351
|
+
|
|
1352
|
+
try:
|
|
1353
|
+
detection_date = datetime.datetime.strptime(detection_date_str, "%Y-%m-%dT%H:%M:%S")
|
|
1354
|
+
except ValueError:
|
|
1355
|
+
try:
|
|
1356
|
+
detection_date = datetime.datetime.strptime(detection_date_str, "%Y-%m-%d")
|
|
1357
|
+
except ValueError:
|
|
1358
|
+
self.isPoam = False
|
|
1359
|
+
return
|
|
1360
|
+
|
|
1361
|
+
days_since_detection = (current_date - detection_date).days
|
|
1362
|
+
|
|
1363
|
+
# Define scan sources
|
|
1364
|
+
scan_sources = {"Vulnerability Assessment", "FDCC/USGCB", "Penetration Test"}
|
|
1365
|
+
is_scan = self.identification in scan_sources
|
|
1366
|
+
|
|
1367
|
+
# Apply standard-specific logic
|
|
1368
|
+
if standard == "fedramp":
|
|
1369
|
+
# FedRAMP: High/Critical are always POAMs if open
|
|
1370
|
+
if severity in {IssueSeverity.High, IssueSeverity.Critical}:
|
|
1371
|
+
self.isPoam = self.status in open_statuses
|
|
1372
|
+
# Scan-based: POAM if overdue
|
|
1373
|
+
elif is_scan:
|
|
1374
|
+
self.isPoam = days_since_detection > threshold_days
|
|
1375
|
+
# Non-scan: POAM if open
|
|
1376
|
+
else:
|
|
1377
|
+
self.isPoam = self.status in open_statuses
|
|
1378
|
+
|
|
1379
|
+
# Handle vendor dependencies
|
|
1380
|
+
if self.vendorDependency and self.vendorLastUpdate:
|
|
1381
|
+
try:
|
|
1382
|
+
vendor_date = datetime.datetime.strptime(self.vendorLastUpdate, "%Y-%m-%dT%H:%M:%S")
|
|
1383
|
+
days_since_vendor = (current_date - vendor_date).days
|
|
1384
|
+
self.isPoam = days_since_vendor > threshold_days
|
|
1385
|
+
except ValueError:
|
|
1386
|
+
pass # Fall back to detection date logic
|
|
1387
|
+
|
|
1388
|
+
else: # NIST 800-53
|
|
1389
|
+
# NIST: POAM for any open deficiency
|
|
1390
|
+
self.isPoam = self.status in open_statuses
|
|
1391
|
+
|
|
1392
|
+
# Apply status filter from config if specified
|
|
1393
|
+
if "status" in config:
|
|
1394
|
+
self.isPoam = self.isPoam and self.status == config["status"]
|
|
1395
|
+
|
|
1273
1396
|
|
|
1274
1397
|
def build_issue_dict_from_query(a: Dict[str, Any]) -> Dict[str, Any]:
|
|
1275
1398
|
"""
|
|
@@ -24,7 +24,7 @@ class Milestone(RegScaleModel):
|
|
|
24
24
|
completed: Optional[bool] = False
|
|
25
25
|
dateCompleted: Optional[str] = Field(default_factory=get_current_datetime)
|
|
26
26
|
notes: Optional[str] = ""
|
|
27
|
-
parentID: Optional[
|
|
27
|
+
parentID: Optional[int] = None
|
|
28
28
|
parentModule: str = ""
|
|
29
29
|
|
|
30
30
|
@staticmethod
|
|
@@ -28,6 +28,7 @@ class RBAC(RegScaleModel):
|
|
|
28
28
|
add="/api/{model_slug}/add/{moduleId}/{parentId}/{groupId}/{permissionType}",
|
|
29
29
|
public="/api/{model_slug}/public/{moduleId}/{parentId}/{public}",
|
|
30
30
|
none_standard_delete="/api/{model_slug}/{moduleId}/{parentId}/{rbacId}",
|
|
31
|
+
reset="/api/{model_slug}/reset/{moduleId}/{parentId}",
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
@classmethod
|
|
@@ -129,3 +130,24 @@ class RBAC(RegScaleModel):
|
|
|
129
130
|
else:
|
|
130
131
|
cls.log_response_error(response=response)
|
|
131
132
|
return False
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def reset(cls, module_id: int, parent_id: int) -> bool:
|
|
136
|
+
"""
|
|
137
|
+
Proliferates the RBAC entry to all its children
|
|
138
|
+
|
|
139
|
+
:param int module_id: The ID of the module
|
|
140
|
+
:param int parent_id: The ID of the parent
|
|
141
|
+
:return: True if the RBAC entry was added, False otherwise
|
|
142
|
+
:rtype: bool
|
|
143
|
+
"""
|
|
144
|
+
response = cls._get_api_handler().get(
|
|
145
|
+
endpoint=cls.get_endpoint("reset").format(
|
|
146
|
+
model_slug=cls._module_slug, moduleId=module_id, parentId=parent_id
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
if response and response.ok:
|
|
150
|
+
return True
|
|
151
|
+
else:
|
|
152
|
+
cls.log_response_error(response=response)
|
|
153
|
+
return False
|
|
@@ -1279,7 +1279,9 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
1279
1279
|
exc_info=True,
|
|
1280
1280
|
)
|
|
1281
1281
|
if response and not response.ok:
|
|
1282
|
-
logger.error(
|
|
1282
|
+
logger.error(
|
|
1283
|
+
f"Response Error: Code #{response.status_code}: {response.reason}\n{response.text}", exc_info=True
|
|
1284
|
+
)
|
|
1283
1285
|
if response is None:
|
|
1284
1286
|
error_msg = "Response was None"
|
|
1285
1287
|
logger.error(error_msg)
|
|
@@ -1522,7 +1524,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
1522
1524
|
:return: A list of objects
|
|
1523
1525
|
:rtype: List[T]
|
|
1524
1526
|
"""
|
|
1525
|
-
response = cls._get_api_handler().get(endpoint=cls.get_endpoint("list"))
|
|
1527
|
+
response = cls._get_api_handler().get(endpoint=cls.get_endpoint("list").format(module_slug=cls._module_slug))
|
|
1526
1528
|
if response.ok:
|
|
1527
1529
|
return cast(List[T], [cls.get_object(object_id=sp["id"]) for sp in response.json()])
|
|
1528
1530
|
else:
|
regscale/utils/graphql_client.py
CHANGED
|
@@ -5,6 +5,8 @@ A module for making paginated GraphQL queries.
|
|
|
5
5
|
import logging
|
|
6
6
|
from typing import List, Dict, Optional, Any
|
|
7
7
|
|
|
8
|
+
import graphql
|
|
9
|
+
|
|
8
10
|
from regscale.core.app.utils.app_utils import create_progress_object, error_and_exit
|
|
9
11
|
|
|
10
12
|
logger = logging.getLogger(__name__)
|
|
@@ -90,7 +92,7 @@ class PaginatedGraphQLClient:
|
|
|
90
92
|
return result
|
|
91
93
|
except Exception as e:
|
|
92
94
|
logger.error(f"An error occurred while executing the query: {str(e)}", exc_info=True)
|
|
93
|
-
logger.error(f"Query: {self.query}")
|
|
95
|
+
logger.error(f"Query: {graphql.print_ast(self.query)}")
|
|
94
96
|
error_and_exit(f"Variable: {variables}")
|
|
95
97
|
|
|
96
98
|
def fetch_results(self, variables: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: regscale-cli
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.21.1.0
|
|
4
4
|
Summary: Command Line Interface (CLI) for bulk processing/loading data into RegScale
|
|
5
5
|
Home-page: https://github.com/RegScale/regscale-cli
|
|
6
6
|
Author: Travis Howerton
|
|
@@ -56,7 +56,7 @@ Requires-Dist: pydantic ~=2.11.0
|
|
|
56
56
|
Requires-Dist: pypandoc
|
|
57
57
|
Requires-Dist: pypandoc-binary
|
|
58
58
|
Requires-Dist: pytest
|
|
59
|
-
Requires-Dist: python-dateutil ~=2.
|
|
59
|
+
Requires-Dist: python-dateutil ~=2.9.0
|
|
60
60
|
Requires-Dist: python-docx
|
|
61
61
|
Requires-Dist: python-jwt ==4.1.0
|
|
62
62
|
Requires-Dist: pyxnat ==1.5.*
|
|
@@ -137,7 +137,7 @@ Requires-Dist: pydantic ~=2.11.0 ; extra == 'airflow'
|
|
|
137
137
|
Requires-Dist: pypandoc ; extra == 'airflow'
|
|
138
138
|
Requires-Dist: pypandoc-binary ; extra == 'airflow'
|
|
139
139
|
Requires-Dist: pytest ; extra == 'airflow'
|
|
140
|
-
Requires-Dist: python-dateutil ~=2.
|
|
140
|
+
Requires-Dist: python-dateutil ~=2.9.0 ; extra == 'airflow'
|
|
141
141
|
Requires-Dist: python-docx ; extra == 'airflow'
|
|
142
142
|
Requires-Dist: python-jwt ==4.1.0 ; extra == 'airflow'
|
|
143
143
|
Requires-Dist: pyxnat ==1.5.* ; extra == 'airflow'
|
|
@@ -222,7 +222,7 @@ Requires-Dist: pydantic ~=2.11.0 ; extra == 'airflow-azure'
|
|
|
222
222
|
Requires-Dist: pypandoc ; extra == 'airflow-azure'
|
|
223
223
|
Requires-Dist: pypandoc-binary ; extra == 'airflow-azure'
|
|
224
224
|
Requires-Dist: pytest ; extra == 'airflow-azure'
|
|
225
|
-
Requires-Dist: python-dateutil ~=2.
|
|
225
|
+
Requires-Dist: python-dateutil ~=2.9.0 ; extra == 'airflow-azure'
|
|
226
226
|
Requires-Dist: python-docx ; extra == 'airflow-azure'
|
|
227
227
|
Requires-Dist: python-jwt ==4.1.0 ; extra == 'airflow-azure'
|
|
228
228
|
Requires-Dist: pyxnat ==1.5.* ; extra == 'airflow-azure'
|
|
@@ -307,7 +307,7 @@ Requires-Dist: pyodbc ; extra == 'airflow-sqlserver'
|
|
|
307
307
|
Requires-Dist: pypandoc ; extra == 'airflow-sqlserver'
|
|
308
308
|
Requires-Dist: pypandoc-binary ; extra == 'airflow-sqlserver'
|
|
309
309
|
Requires-Dist: pytest ; extra == 'airflow-sqlserver'
|
|
310
|
-
Requires-Dist: python-dateutil ~=2.
|
|
310
|
+
Requires-Dist: python-dateutil ~=2.9.0 ; extra == 'airflow-sqlserver'
|
|
311
311
|
Requires-Dist: python-docx ; extra == 'airflow-sqlserver'
|
|
312
312
|
Requires-Dist: python-jwt ==4.1.0 ; extra == 'airflow-sqlserver'
|
|
313
313
|
Requires-Dist: pyxnat ==1.5.* ; extra == 'airflow-sqlserver'
|
|
@@ -397,7 +397,7 @@ Requires-Dist: pydantic ~=2.11.0 ; extra == 'all'
|
|
|
397
397
|
Requires-Dist: pypandoc ; extra == 'all'
|
|
398
398
|
Requires-Dist: pypandoc-binary ; extra == 'all'
|
|
399
399
|
Requires-Dist: pytest ; extra == 'all'
|
|
400
|
-
Requires-Dist: python-dateutil ~=2.
|
|
400
|
+
Requires-Dist: python-dateutil ~=2.9.0 ; extra == 'all'
|
|
401
401
|
Requires-Dist: python-docx ; extra == 'all'
|
|
402
402
|
Requires-Dist: python-jwt ==4.1.0 ; extra == 'all'
|
|
403
403
|
Requires-Dist: pyxnat ==1.5.* ; extra == 'all'
|
|
@@ -460,7 +460,7 @@ Requires-Dist: pydantic ~=2.11.0 ; extra == 'ansible'
|
|
|
460
460
|
Requires-Dist: pypandoc ; extra == 'ansible'
|
|
461
461
|
Requires-Dist: pypandoc-binary ; extra == 'ansible'
|
|
462
462
|
Requires-Dist: pytest ; extra == 'ansible'
|
|
463
|
-
Requires-Dist: python-dateutil ~=2.
|
|
463
|
+
Requires-Dist: python-dateutil ~=2.9.0 ; extra == 'ansible'
|
|
464
464
|
Requires-Dist: python-docx ; extra == 'ansible'
|
|
465
465
|
Requires-Dist: python-jwt ==4.1.0 ; extra == 'ansible'
|
|
466
466
|
Requires-Dist: pyxnat ==1.5.* ; extra == 'ansible'
|
|
@@ -539,7 +539,7 @@ Requires-Dist: pytest-rerunfailures ; extra == 'dev'
|
|
|
539
539
|
Requires-Dist: pytest-timeout ; extra == 'dev'
|
|
540
540
|
Requires-Dist: pytest-xdist ; extra == 'dev'
|
|
541
541
|
Requires-Dist: pytest >=5 ; extra == 'dev'
|
|
542
|
-
Requires-Dist: python-dateutil ~=2.
|
|
542
|
+
Requires-Dist: python-dateutil ~=2.9.0 ; extra == 'dev'
|
|
543
543
|
Requires-Dist: python-docx ; extra == 'dev'
|
|
544
544
|
Requires-Dist: python-jwt ==4.1.0 ; extra == 'dev'
|
|
545
545
|
Requires-Dist: pyxnat ==1.5.* ; extra == 'dev'
|
|
@@ -612,7 +612,7 @@ Requires-Dist: pydantic ~=2.11.0 ; extra == 'server'
|
|
|
612
612
|
Requires-Dist: pypandoc ; extra == 'server'
|
|
613
613
|
Requires-Dist: pypandoc-binary ; extra == 'server'
|
|
614
614
|
Requires-Dist: pytest ; extra == 'server'
|
|
615
|
-
Requires-Dist: python-dateutil ~=2.
|
|
615
|
+
Requires-Dist: python-dateutil ~=2.9.0 ; extra == 'server'
|
|
616
616
|
Requires-Dist: python-docx ; extra == 'server'
|
|
617
617
|
Requires-Dist: python-jwt ==4.1.0 ; extra == 'server'
|
|
618
618
|
Requires-Dist: pyxnat ==1.5.* ; extra == 'server'
|