Refactor code structure for improved readability and maintainability; optimize performance in key functions.

This commit is contained in:
master
2025-12-22 19:06:31 +02:00
parent dfaa2079aa
commit 4602ccc3a3
1444 changed files with 109919 additions and 8058 deletions

View File

@@ -1,33 +1,32 @@
# Scanner Drift API Reference
# Scanner Drift API Reference
**Module:** Scanner
**Version:** 1.0
**Base Path:** `/api/scanner`
**Base Path:** `/api/v1`
**Last Updated:** 2025-12-22
---
## 1. Overview
The Scanner Drift API provides endpoints for computing and retrieving reachability drift analysis between scans. Drift detection identifies when code changes create new paths to vulnerable sinks or mitigate existing risks.
The Scanner Drift API computes and retrieves reachability drift between scans. Drift detection identifies when code changes introduce new paths to sensitive sinks or remove existing paths.
---
## 2. Authentication & Authorization
## 2. Authentication and Authorization
### Required Scopes
| Endpoint | Scope |
|----------|-------|
| Read drift results | `scanner:read` |
| Compute reachability | `scanner:write` |
| Admin operations | `scanner:admin` |
|---|---|
| Read drift results | `scanner.scans.read` |
| Compute reachability | `scanner.scans.write` |
### Headers
```http
Authorization: Bearer <access_token>
X-Tenant-Id: <tenant_uuid>
X-Tenant-Id: <tenant_uuid> # optional fallback for rate limiting
```
---
@@ -36,68 +35,32 @@ X-Tenant-Id: <tenant_uuid>
### 3.1 GET /scans/{scanId}/drift
Retrieves drift analysis results comparing the specified scan against its base scan.
Returns drift results for the scan. If `baseScanId` is provided, drift is computed and stored. If omitted, the most recent stored drift result is returned.
**Parameters:**
**Parameters**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| scanId | path | string | Yes | Head scan identifier |
| baseScanId | query | string | No | Base scan ID (defaults to previous scan) |
| language | query | string | No | Filter by language (dotnet, node, java, etc.) |
|---|---|---|---|---|
| scanId | path | string | yes | Head scan identifier |
| baseScanId | query | string | no | Base scan identifier |
| language | query | string | no | Language (default: `dotnet`) |
| includeFullPath | query | boolean | no | Include full path nodes in compressed paths |
**Response: 200 OK**
```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"baseScanId": "abc123",
"headScanId": "def456",
"baseScanId": "base123",
"headScanId": "head456",
"language": "dotnet",
"detectedAt": "2025-12-22T10:30:00Z",
"newlyReachableCount": 3,
"newlyUnreachableCount": 1,
"totalDriftCount": 4,
"hasMaterialDrift": true,
"resultDigest": "sha256:a1b2c3d4..."
}
```
**Response: 404 Not Found**
```json
{
"error": "DRIFT_NOT_FOUND",
"message": "No drift analysis found for scan def456"
}
```
---
### 3.2 GET /drift/{driftId}/sinks
Retrieves individual drifted sinks with pagination.
**Parameters:**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| driftId | path | uuid | Yes | Drift result identifier |
| direction | query | string | No | Filter: `became_reachable` or `became_unreachable` |
| sinkCategory | query | string | No | Filter by sink category |
| offset | query | int | No | Pagination offset (default: 0) |
| limit | query | int | No | Page size (default: 100, max: 1000) |
**Response: 200 OK**
```json
{
"items": [
"newlyReachable": [
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"sinkNodeId": "MyApp.Services.DbService.ExecuteQuery(string)",
"symbol": "DbService.ExecuteQuery",
"sinkCategory": "sql_raw",
"sinkCategory": "SQL_RAW",
"direction": "became_reachable",
"cause": {
"kind": "guard_removed",
@@ -112,13 +75,19 @@ Retrieves individual drifted sinks with pagination.
"nodeId": "MyApp.Controllers.UserController.GetUser(int)",
"symbol": "UserController.GetUser",
"file": "src/Controllers/UserController.cs",
"line": 15
"line": 15,
"package": "app",
"isChanged": false,
"changeKind": null
},
"sink": {
"nodeId": "MyApp.Services.DbService.ExecuteQuery(string)",
"symbol": "DbService.ExecuteQuery",
"file": "src/Services/DbService.cs",
"line": 88
"line": 88,
"package": "app",
"isChanged": false,
"changeKind": null
},
"intermediateCount": 3,
"keyNodes": [
@@ -127,25 +96,90 @@ Retrieves individual drifted sinks with pagination.
"symbol": "AuthMiddleware.Validate",
"file": "src/Middleware/AuthMiddleware.cs",
"line": 42,
"package": "app",
"isChanged": true,
"changeKind": "guard_changed"
}
]
},
"associatedVulns": [
{
"cveId": "CVE-2024-12345",
"epss": 0.85,
"cvss": 9.8,
"vexStatus": "affected",
"packagePurl": "pkg:nuget/Dapper@2.0.123"
}
]
"associatedVulns": []
}
],
"totalCount": 3,
"newlyUnreachable": [],
"resultDigest": "sha256:a1b2c3d4...",
"totalDriftCount": 1,
"hasMaterialDrift": true
}
```
**Response: 404 Not Found**
Returned if the scan or drift result is missing or if call graph snapshots are not available.
---
### 3.2 GET /drift/{driftId}/sinks
Returns drifted sinks for a drift result.
**Parameters**
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| driftId | path | uuid | yes | Drift result identifier |
| direction | query | string | no | `became_reachable` or `became_unreachable` |
| offset | query | integer | no | Offset (default: 0) |
| limit | query | integer | no | Page size (default: 100, max: 500) |
**Response: 200 OK**
```json
{
"driftId": "550e8400-e29b-41d4-a716-446655440000",
"direction": "became_reachable",
"offset": 0,
"limit": 100
"limit": 100,
"count": 1,
"sinks": [
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"sinkNodeId": "MyApp.Services.DbService.ExecuteQuery(string)",
"symbol": "DbService.ExecuteQuery",
"sinkCategory": "SQL_RAW",
"direction": "became_reachable",
"cause": {
"kind": "guard_removed",
"description": "Guard condition removed in AuthMiddleware.Validate",
"changedSymbol": "AuthMiddleware.Validate",
"changedFile": "src/Middleware/AuthMiddleware.cs",
"changedLine": 42,
"codeChangeId": "770e8400-e29b-41d4-a716-446655440002"
},
"path": {
"entrypoint": {
"nodeId": "MyApp.Controllers.UserController.GetUser(int)",
"symbol": "UserController.GetUser",
"file": "src/Controllers/UserController.cs",
"line": 15,
"package": "app",
"isChanged": false,
"changeKind": null
},
"sink": {
"nodeId": "MyApp.Services.DbService.ExecuteQuery(string)",
"symbol": "DbService.ExecuteQuery",
"file": "src/Services/DbService.cs",
"line": 88,
"package": "app",
"isChanged": false,
"changeKind": null
},
"intermediateCount": 3,
"keyNodes": []
},
"associatedVulns": []
}
]
}
```
@@ -153,21 +187,21 @@ Retrieves individual drifted sinks with pagination.
### 3.3 POST /scans/{scanId}/compute-reachability
Triggers reachability computation for a scan. Idempotent - returns cached result if already computed.
Triggers reachability computation for a scan.
**Parameters:**
**Parameters**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| scanId | path | string | Yes | Scan identifier |
|---|---|---|---|---|
| scanId | path | string | yes | Scan identifier |
**Request Body:**
**Request Body**
```json
{
"languages": ["dotnet", "node"],
"baseScanId": "abc123",
"forceRecompute": false
"forceRecompute": false,
"entrypoints": ["MyApp.Controllers.UserController.GetUser"],
"targets": ["pkg:nuget/Dapper@2.0.123"]
}
```
@@ -175,37 +209,29 @@ Triggers reachability computation for a scan. Idempotent - returns cached result
```json
{
"jobId": "880e8400-e29b-41d4-a716-446655440003",
"status": "queued",
"estimatedCompletionSeconds": 30
"jobId": "reachability_head456",
"status": "scheduled",
"estimatedDuration": null
}
```
**Response: 200 OK** (cached result)
**Response: 409 Conflict**
```json
{
"jobId": "880e8400-e29b-41d4-a716-446655440003",
"status": "completed",
"driftResultId": "550e8400-e29b-41d4-a716-446655440000"
}
```
Returned when computation is already in progress for the scan.
---
### 3.4 GET /scans/{scanId}/reachability/components
Lists components with their reachability status.
Lists components with reachability status.
**Parameters:**
**Parameters**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| scanId | path | string | Yes | Scan identifier |
| language | query | string | No | Filter by language |
| reachable | query | bool | No | Filter by reachability |
| offset | query | int | No | Pagination offset |
| limit | query | int | No | Page size |
|---|---|---|---|---|
| scanId | path | string | yes | Scan identifier |
| purl | query | string | no | Filter by PURL |
| status | query | string | no | Filter by status |
**Response: 200 OK**
@@ -214,17 +240,13 @@ Lists components with their reachability status.
"items": [
{
"purl": "pkg:nuget/Newtonsoft.Json@13.0.1",
"language": "dotnet",
"reachableSinkCount": 2,
"unreachableSinkCount": 5,
"totalSinkCount": 7,
"highestSeveritySink": "unsafe_deser",
"reachabilityGate": 5
"status": "reachable",
"confidence": 0.92,
"latticeState": "confirmed",
"why": ["entrypoint:UserController.GetUser"]
}
],
"totalCount": 42,
"offset": 0,
"limit": 100
"total": 1
}
```
@@ -232,17 +254,15 @@ Lists components with their reachability status.
### 3.5 GET /scans/{scanId}/reachability/findings
Lists reachable vulnerable sinks with CVE associations.
Lists reachability findings for CVEs.
**Parameters:**
**Parameters**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| scanId | path | string | Yes | Scan identifier |
| minCvss | query | float | No | Minimum CVSS score |
| kevOnly | query | bool | No | Only KEV vulnerabilities |
| offset | query | int | No | Pagination offset |
| limit | query | int | No | Page size |
|---|---|---|---|---|
| scanId | path | string | yes | Scan identifier |
| cve | query | string | no | Filter by CVE |
| status | query | string | no | Filter by status |
**Response: 200 OK**
@@ -250,25 +270,16 @@ Lists reachable vulnerable sinks with CVE associations.
{
"items": [
{
"sinkNodeId": "MyApp.Services.CryptoService.Encrypt(string)",
"symbol": "CryptoService.Encrypt",
"sinkCategory": "crypto_weak",
"isReachable": true,
"shortestPathLength": 4,
"vulnerabilities": [
{
"cveId": "CVE-2024-54321",
"cvss": 7.5,
"epss": 0.42,
"isKev": false,
"vexStatus": "affected"
}
]
"cveId": "CVE-2024-12345",
"purl": "pkg:nuget/Dapper@2.0.123",
"status": "reachable",
"confidence": 0.81,
"latticeState": "likely",
"severity": "critical",
"affectedVersions": "< 2.0.200"
}
],
"totalCount": 15,
"offset": 0,
"limit": 100
"total": 1
}
```
@@ -276,139 +287,123 @@ Lists reachable vulnerable sinks with CVE associations.
### 3.6 GET /scans/{scanId}/reachability/explain
Explains why a specific sink is reachable or unreachable.
Explains reachability for a CVE and PURL.
**Parameters:**
**Parameters**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| scanId | path | string | Yes | Scan identifier |
| sinkNodeId | query | string | Yes | Sink node identifier |
| includeFullPath | query | bool | No | Include full path (default: false) |
|---|---|---|---|---|
| scanId | path | string | yes | Scan identifier |
| cve | query | string | yes | CVE identifier |
| purl | query | string | yes | Package URL |
**Response: 200 OK**
```json
{
"sinkNodeId": "MyApp.Services.DbService.ExecuteQuery(string)",
"isReachable": true,
"reachabilityGate": 6,
"confidence": "confirmed",
"explanation": "Sink is reachable from 2 HTTP entrypoints via direct call paths",
"entrypoints": [
{
"nodeId": "MyApp.Controllers.UserController.GetUser(int)",
"entrypointType": "http_handler",
"pathLength": 4
},
{
"nodeId": "MyApp.Controllers.AdminController.Query(string)",
"entrypointType": "http_handler",
"pathLength": 2
}
"cveId": "CVE-2024-12345",
"purl": "pkg:nuget/Dapper@2.0.123",
"status": "reachable",
"confidence": 0.81,
"latticeState": "likely",
"pathWitness": ["entrypoint:UserController.GetUser", "sink:Dapper.Query"],
"why": [
{ "code": "call_graph", "description": "Path exists from HTTP entrypoint", "impact": 0.6 }
],
"shortestPath": {
"entrypoint": {...},
"sink": {...},
"intermediateCount": 1,
"keyNodes": [...]
"evidence": {
"staticAnalysis": {
"callgraphDigest": "sha256:...",
"pathLength": 4,
"edgeTypes": ["direct", "virtual"]
},
"runtimeEvidence": {
"observed": false,
"hitCount": 0,
"lastObserved": null
},
"policyEvaluation": {
"policyDigest": "sha256:...",
"verdict": "block",
"verdictReason": "delta_reachable > 0"
}
},
"fullPath": ["node1", "node2", "node3", "sink"]
"spineId": "spine:sha256:..."
}
```
---
## 4. Request/Response Models
## 4. Request and Response Models
### 4.1 DriftDirection
Key models (JSON names shown):
- `ReachabilityDriftResult`: `id`, `baseScanId`, `headScanId`, `language`, `detectedAt`, `newlyReachable`, `newlyUnreachable`, `resultDigest`, `totalDriftCount`, `hasMaterialDrift`.
- `DriftedSink`: `id`, `sinkNodeId`, `symbol`, `sinkCategory`, `direction`, `cause`, `path`, `associatedVulns`.
- `DriftCause`: `kind`, `description`, `changedSymbol`, `changedFile`, `changedLine`, `codeChangeId`.
- `CompressedPath`: `entrypoint`, `sink`, `intermediateCount`, `keyNodes`, `fullPath` (optional).
- `PathNode`: `nodeId`, `symbol`, `file`, `line`, `package`, `isChanged`, `changeKind`.
- `ComputeReachabilityRequestDto`: `forceRecompute`, `entrypoints`, `targets`.
- `ComputeReachabilityResponseDto`: `jobId`, `status`, `estimatedDuration`.
```typescript
enum DriftDirection {
became_reachable = "became_reachable",
became_unreachable = "became_unreachable"
}
---
## 5. Enumerations
### DriftDirection
```text
became_reachable
became_unreachable
```
### 4.2 DriftCauseKind
```typescript
enum DriftCauseKind {
guard_removed = "guard_removed",
guard_added = "guard_added",
new_public_route = "new_public_route",
visibility_escalated = "visibility_escalated",
dependency_upgraded = "dependency_upgraded",
symbol_removed = "symbol_removed",
unknown = "unknown"
}
### DriftCauseKind
```text
guard_removed
guard_added
new_public_route
visibility_escalated
dependency_upgraded
symbol_removed
unknown
```
### 4.3 SinkCategory
```typescript
enum SinkCategory {
cmd_exec = "cmd_exec",
unsafe_deser = "unsafe_deser",
sql_raw = "sql_raw",
ssrf = "ssrf",
file_write = "file_write",
path_traversal = "path_traversal",
template_injection = "template_injection",
crypto_weak = "crypto_weak",
authz_bypass = "authz_bypass",
ldap_injection = "ldap_injection",
xpath_injection = "xpath_injection",
xxe_injection = "xxe_injection",
code_injection = "code_injection",
log_injection = "log_injection",
reflection = "reflection",
open_redirect = "open_redirect"
}
### CodeChangeKind
```text
added
removed
signature_changed
guard_changed
dependency_changed
visibility_changed
```
### 4.4 CodeChangeKind
```typescript
enum CodeChangeKind {
added = "added",
removed = "removed",
signature_changed = "signature_changed",
guard_changed = "guard_changed",
dependency_changed = "dependency_changed",
visibility_changed = "visibility_changed"
}
### SinkCategory
```text
CMD_EXEC
UNSAFE_DESER
SQL_RAW
SSRF
FILE_WRITE
PATH_TRAVERSAL
TEMPLATE_INJECTION
CRYPTO_WEAK
AUTHZ_BYPASS
LDAP_INJECTION
XPATH_INJECTION
XXE
CODE_INJECTION
LOG_INJECTION
REFLECTION
OPEN_REDIRECT
```
---
## 5. Error Codes
## 6. Errors
| Code | HTTP Status | Description |
|------|-------------|-------------|
| `SCAN_NOT_FOUND` | 404 | Scan ID does not exist |
| `DRIFT_NOT_FOUND` | 404 | No drift analysis for this scan |
| `GRAPH_NOT_EXTRACTED` | 400 | Call graph not yet extracted |
| `LANGUAGE_NOT_SUPPORTED` | 400 | Language not supported for reachability |
| `COMPUTATION_IN_PROGRESS` | 409 | Reachability computation already running |
| `COMPUTATION_FAILED` | 500 | Reachability computation failed |
| `INVALID_SINK_ID` | 400 | Sink node ID not found in graph |
---
## 6. Rate Limiting
| Endpoint | Rate Limit |
|----------|------------|
| GET endpoints | 100/min |
| POST compute | 10/min |
Rate limit headers:
```http
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1703242800
```
Endpoints return Problem Details (RFC 7807) for errors. Common cases:
- 400: invalid scan identifier, invalid direction, missing query parameters.
- 404: scan not found, call graph snapshot missing, drift result not found.
- 409: reachability computation already in progress.
- 500: unexpected server error.
---
@@ -418,109 +413,32 @@ X-RateLimit-Reset: 1703242800
```bash
curl -X GET \
'https://api.stellaops.example/api/scanner/scans/def456/drift?language=dotnet' \
-H 'Authorization: Bearer <token>' \
-H 'X-Tenant-Id: <tenant_id>'
'https://scanner.example/api/v1/scans/head456/drift?baseScanId=base123&language=dotnet' \
-H 'Authorization: Bearer <token>'
```
### 7.2 cURL - Compute Reachability
### 7.2 cURL - List Drifted Sinks
```bash
curl -X GET \
'https://scanner.example/api/v1/drift/550e8400-e29b-41d4-a716-446655440000/sinks?direction=became_reachable&offset=0&limit=100' \
-H 'Authorization: Bearer <token>'
```
### 7.3 cURL - Compute Reachability
```bash
curl -X POST \
'https://api.stellaops.example/api/scanner/scans/def456/compute-reachability' \
'https://scanner.example/api/v1/scans/head456/compute-reachability' \
-H 'Authorization: Bearer <token>' \
-H 'X-Tenant-Id: <tenant_id>' \
-H 'Content-Type: application/json' \
-d '{
"languages": ["dotnet"],
"baseScanId": "abc123"
}'
```
### 7.3 C# SDK
```csharp
var client = new ScannerClient(options);
// Get drift results
var drift = await client.GetDriftAsync("def456", language: "dotnet");
Console.WriteLine($"Newly reachable: {drift.NewlyReachableCount}");
// Get drifted sinks
var sinks = await client.GetDriftedSinksAsync(drift.Id,
direction: DriftDirection.BecameReachable);
foreach (var sink in sinks.Items)
{
Console.WriteLine($"{sink.Symbol}: {sink.Cause.Description}");
}
```
### 7.4 TypeScript SDK
```typescript
import { ScannerClient } from '@stellaops/sdk';
const client = new ScannerClient({ baseUrl, token });
// Get drift results
const drift = await client.getDrift('def456', { language: 'dotnet' });
console.log(`Newly reachable: ${drift.newlyReachableCount}`);
// Explain a sink
const explanation = await client.explainReachability('def456', {
sinkNodeId: 'MyApp.Services.DbService.ExecuteQuery(string)',
includeFullPath: true
});
console.log(explanation.explanation);
-d '{"forceRecompute": false}'
```
---
## 8. Webhooks
## 8. References
### 8.1 drift.computed
Fired when drift analysis completes.
```json
{
"event": "drift.computed",
"timestamp": "2025-12-22T10:30:00Z",
"data": {
"driftResultId": "550e8400-e29b-41d4-a716-446655440000",
"scanId": "def456",
"baseScanId": "abc123",
"newlyReachableCount": 3,
"newlyUnreachableCount": 1,
"hasMaterialDrift": true
}
}
```
### 8.2 drift.kev_reachable
Fired when a KEV becomes reachable.
```json
{
"event": "drift.kev_reachable",
"timestamp": "2025-12-22T10:30:00Z",
"severity": "critical",
"data": {
"driftResultId": "550e8400-e29b-41d4-a716-446655440000",
"scanId": "def456",
"kevCveId": "CVE-2024-12345",
"sinkNodeId": "..."
}
}
```
---
## 9. References
- **Architecture:** `docs/modules/scanner/reachability-drift.md`
- **Operations:** `docs/operations/reachability-drift-guide.md`
- **Source:** `src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReachabilityDriftEndpoints.cs`
- `docs/modules/scanner/reachability-drift.md`
- `docs/operations/reachability-drift-guide.md`
- `src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReachabilityDriftEndpoints.cs`

View File

@@ -57,6 +57,18 @@ Returns paginated list of unknowns, optionally sorted by score.
"id": "unk-12345678-abcd-1234-5678-abcdef123456",
"artifactDigest": "sha256:abc123...",
"artifactPurl": "pkg:oci/myapp@sha256:abc123",
"reasonCode": "Reachability",
"reasonCodeShort": "U-RCH",
"remediationHint": "Run reachability analysis",
"detailedHint": "Execute call-graph analysis to determine if vulnerable code paths are reachable from application entrypoints.",
"automationCommand": "stella analyze --reachability",
"evidenceRefs": [
{
"type": "reachability",
"uri": "proofs/unknowns/unk-12345678/evidence.json",
"digest": "sha256:0a1b2c..."
}
],
"reasons": ["missing_vex", "ambiguous_indirect_call"],
"blastRadius": {
"dependents": 15,
@@ -118,6 +130,18 @@ Returns detailed information about a specific unknown.
"id": "unk-12345678-abcd-1234-5678-abcdef123456",
"artifactDigest": "sha256:abc123...",
"artifactPurl": "pkg:oci/myapp@sha256:abc123",
"reasonCode": "Reachability",
"reasonCodeShort": "U-RCH",
"remediationHint": "Run reachability analysis",
"detailedHint": "Execute call-graph analysis to determine if vulnerable code paths are reachable from application entrypoints.",
"automationCommand": "stella analyze --reachability",
"evidenceRefs": [
{
"type": "reachability",
"uri": "proofs/unknowns/unk-12345678/evidence.json",
"digest": "sha256:0a1b2c..."
}
],
"reasons": ["missing_vex", "ambiguous_indirect_call"],
"reasonDetails": [
{
@@ -270,15 +294,15 @@ Returns aggregate statistics about unknowns.
## Reason Codes
| Code | Description |
|------|-------------|
| `missing_vex` | No VEX statement for vulnerability |
| `ambiguous_indirect_call` | Indirect call target unresolved |
| `incomplete_sbom` | SBOM missing component data |
| `unknown_platform` | Platform not recognized |
| `missing_advisory` | No advisory data for CVE |
| `conflicting_evidence` | Multiple conflicting data sources |
| `stale_data` | Data exceeds freshness threshold |
| Code | Short Code | Description |
|------|------------|-------------|
| `Reachability` | `U-RCH` | Call path analysis is indeterminate. |
| `Identity` | `U-ID` | Ambiguous package identity or missing digest. |
| `Provenance` | `U-PROV` | Cannot map binary artifact to source repository. |
| `VexConflict` | `U-VEX` | VEX statements conflict or applicability data is missing. |
| `FeedGap` | `U-FEED` | Required advisory/feed coverage missing or stale. |
| `ConfigUnknown` | `U-CONFIG` | Runtime configuration or feature flags not observable. |
| `AnalyzerLimit` | `U-ANALYZER` | Language or framework not supported by analyzer. |
## Score Calculation